This chapter focuses on

  • Why is there dynamic memory allocation
  • Introduction to dynamic memory functions
  • malloc
  • free
  • calloc
  • realloc
  • Common dynamic memory errors
  • A couple of classic pen-based tests
  • The flexible array

The text start


1. Why does dynamic memory allocation exist

We have mastered the memory opening methods are:

int val = 20;// Open up four bytes in stack space
char arr[10] = {0};// Create a contiguous space of 10 bytes in stack space
Copy the code

But the above way of opening up space has two characteristics:

1. The size of space is fixed.

2. When you declare an array, you must specify the length of the array. The memory it needs will be allocated at compile time.

But the need for space goes beyond that. Sometimes the amount of space we need is not known until the program is running, so the way the array is compiled is not enough. At this time can only try dynamic save open up.


2. The introduction of dynamic memory functions

2.1 malloc and free

The C language provides a dynamic memory opening function:

void* malloc (size_t size);

Size_t is an unsigned integer. Its header file is #include

.

This function requests a contiguous chunk of memory and returns a pointer to that chunk of memory.

Note:

  1. If it succeeds, a pointer to the cleared space is returned.

  2. If it fails, a NULL pointer is returned, so the return value of malloc must be checked.

  3. The return value is of type void*, so the malloc function does not know the type of open space

It’s up to you.

  1. If the parametersizeIf 0, malloc’s behavior is standard and undefined, depending on the compiler.

In addition, C language provides another function free, specifically used to do dynamic memory release and reclamation, function prototype is as follows:

void free (void* ptr);

PTR a pointer to a previously allocated block of memory with the same header as malloc: #include

.

Note:

  1. If the PTR argument points to a space that is not dynamically opened, the free function behaves undefined.

  2. If the argument PTR is a NULL pointer, the free function does nothing, so it has no effect on the code.

To avoid wasting space or having the contents of the pointer modified later in the code, we can use the free function and assign the pointer to NULL to prevent these problems, for example:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
int main()
{
	// Request memory for 10 integers
	int* p = (int*)malloc(10 * sizeof(int));
	if (p == NULL)// check whether p is null
	{
		// The error code corresponding to the error message is stored in errno
		printf("%s\n", strerror(errno));
		//strerror Prints the error information corresponding to the error code
	}
	else// Succeeded in creating space
	{
		// Use space normally
		int i = 0;
		for (i = 0; i < 10; i++)
		{
			*(p + i) = i;
		}
		for (i = 0; i < 10; i++)
		{
			printf("%d ", *(p + i)); }}// When dynamically requested space is no longer used, it should be returned to the operating system
	free(p);// Release the dynamic memory pointed to by p
	p = NULL;// Prevent the contents pointed to by p from being modified
	return 0;
}

Copy the code

The output result when the space is successfully opened:

Output when space creation fails:


2.2 calloc

C also provides a function called calloc, which is also used for dynamic memory allocation. The prototype is as follows:

void* calloc (size_t num, size_t size);

Size_t num is the number of elements, size is the type size of each element, and the header file is #include

, just like malloc and free.

The difference between:

  1. The function of the calloc function isnumA size ofsizeThe elements open up a space, andInitialize each byte of space to 0.
  2. It differs from malloc only in that calloc initializes every byte in the requested space to all zeros before returning the address.

Such as:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
int main()
{
	int i = 0;
	int* p = (int*)calloc(10, sizeof(int));
	// Initialize the contents of memory to 0 before applying for space

	if (p == NULL)// check whether p is null
	{
		// The error code corresponding to the error message is stored in errno
		printf("%s\n", strerror(errno));
		//strerror Prints the error information corresponding to the error code
	}
	else// Succeeded in creating space
	{
		// Use space normally
		for (i = 0; i < 10; i++)
		{
			printf("%d ", *(p + i)); }}// When dynamically requested space is no longer used, it should be returned to the operating system
	free(p);// Release the dynamic memory pointed to by p
	p = NULL;// Prevent the contents pointed to by p from being modified

	return 0;
}
Copy the code

Output result:

So if we need to initialize the contents of the requested memory space, then we can easily use the calloc function to complete the task.


2.3 realloc

The advent of realloc functions makes dynamic memory management more flexible. Sometimes we will find that the space applied in the past is too small, sometimes we will feel that the space applied is too large, in order to reasonably apply for memory, we will make flexible adjustment to the size of memory. The realloc function can be used to adjust the size of the dynamically opened memory. The function prototype is as follows:

void* realloc (void* ptr, size_t size);

PTR indicates the memory address to be adjusted, and size is the new size after the adjustment. If the append succeeds, it will return the starting position of the adjusted memory. If the append fails, it will return a null pointer.

Such as:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
int main()
{
	int i = 0;
	int* p = (int*)malloc(5 * sizeof(int));
	// Create 20 bytes of space
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
	}
	else
	{
		for (i = 0; i < 5; i++)
		{
			*(p + i) = i;
		}
		// Use malloc to create 20 bytes of space
	}
	// Suppose here, 20 bytes is not enough for our use
	// We want 40 bytes of space
	// Realloc can be used here to adjust dynamically allocated memory
	int* p2 =(int*) realloc(p, 40);
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *(p2 + i));
	}// If five random values are displayed after 0, 1, 2, 3, and 4 are printed, the space is successfully created

	return 0;
}
Copy the code

Output result:

But writing code like this can be quite risky, so be careful when using realloc functions:

  1. If there is enough memory space to append to the space pointed to by P, the append will directly append to the space pointed to by P, and return the address of P.
  2. If there is not enough memory to add to the space pointed to by P, the realloc function will find a new area of memory, create a new area of memory, copy the data in the original memory, free the old space, and finally return the address of the new space.
  3. In case the space fails, we use a new pointer variable to accept the return value of the Realloc function.

Correct way to write it:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
int main()
{
	int i = 0;
	int* p = (int*)malloc(5 * sizeof(int));
	// Open up space for five integers
	for (i = 0; i < 5; i++)
	{
		*(p + i) = i;
	}
	// Assign values to the five integer variables
	int* ptr = (int*)realloc(p, 40);
	// Not enough space is created. Use realloc to create a new space
	// PTR stores the address of the new space created
	if(ptr ! = NULL)// Determine whether the opening is successful
	{
		p = ptr;
		// In case the space fails to open (p = null),
                // Use a new pointer variable PTR to accept the return value of the realloc function
		for (i = 5; i < 10; i++)
		{
			*(p + i) = i;
		}
		for (i = 0; i < 10; i++)
		{
			printf("%d ", *(p + i)); }}else
	{
		printf("%s\n", strerror(errno));
	}
	free(p);// Free up space after use
	p = NULL;// Reassign the used space to a null pointer

	return 0;
}
Copy the code

Output result:


Common dynamic memory errors

3.1 Dereferencing NULL Pointers

Examples of errors:

#include<stdlib.h>
int main()
{
	int* p = (int*)malloc(INT_MAX / 4);
	*p = 20;// If p is NULL, there is a problem
	free(p);

	return 0;
}
Copy the code

NULL Pointers cannot be dereferenced

Warning:


3.2 Cross-border access to dynamic open space

Examples of errors:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
int main()
{
	int i = 0;
	int* p = (int*)malloc(10 * sizeof(int));
	// Create space for 10 integers
	if (NULL == p)
	{
		exit(EXIT_FAILURE);
	}
	for (i = 0; i <= 10; i++)
	{
		*(p + i) = i;// When I is 10, access is out of bounds
	}
	free(p);

	return 0;
}
Copy the code

Error:

When creating space, you need to pay attention to whether there is enough space. If not, you can use the realloc function we talked about earlier to solve the problem.


3.3 Use free to release non-dynamically allocated memory

Examples of errors:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
int main()
{
	int a = 10;
	int* p = &a;
	free(p);
	// p does not refer to the memory address of the dynamically opened space

	return 0;
}
Copy the code

Error still reported:

You don’t always have to use the free function, all right


3.4 Using free to free a portion of dynamically allocated memory

Examples of errors:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
int main()
{
	int* p = (int*)malloc(100);
	p++;
	free(p);
	//p no longer points to the starting location of dynamic memory

	return 0;
}
Copy the code

Still get an error:

When using the free function, note that the contents in parentheses must be the starting address of the space previously opened


3.5 Releasing the Same Block of Dynamic Memory Multiple times

Examples of errors:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
int main()
{
	int* p = (int*)malloc(100);
	free(p);
	free(p);// Repeat release

	return 0;
}
Copy the code

Still error:

To avoid this problem, we can assign the starting address to a NULL pointer every time we use the free function to free the space.


3.6 Dynamic Memory Opening and Forgetting to Release (Memory Leakage)

Examples of errors:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
void test()
{
	int* p = (int*)malloc(100);
	if(NULL ! = p) {return;
	}
}
int main()
{
	test();

	return 0;
}
Copy the code

A memory leak can be negligible, but the accumulation of memory leaks can be serious, no matter how much memory is used up sooner or later. So we must remember: dynamic space must be released, and the correct release.


4. Some classic pen-based tests

4.1 Topic 1:

Can I copy it successfully?

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void GetMemory(char* p)
{
	p = (char*)malloc(100);// Dynamic memory is opened
}

void Test(void)
{
	char* str = NULL;
	GetMemory(str);/ / value
	strcpy(str, "hello world");// Copy the string
	printf(str);

}

int main()
{
	Test();

	return 0;
}
Copy the code

Output result:

Obviously not, so why?

STR is passed to GetMemory as a value, so the parameter p is a temporary copy of STR. The STR is still NULL when the GetMemory function returns, so the copy of strcpy fails. When the GetMemory function returns, the p parameter is destroyed, and the address of the 100 bytes is lost, causing the 100 bytes to leak and cannot be freed.

So if we want to copy it successfully, what should we do? The code is as follows:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
char* GetMemory(char* p)
{
	p = (char*)malloc(100);
	return p;// return the address of the dynamically opened space
}

void Test(void)
{
	char* str = NULL;
	str = GetMemory(str);
	strcpy(str, "hello world");
	printf(str);
	free(str);// Free up space to prevent memory leaks
	str = NULL;
}

int main()
{
	Test();

	return 0;
}
Copy the code

Output result:

Or use a secondary pointer to change the code as follows:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void GetMemory(char** p)// Secondary pointer receive
{
	*p = (char*)malloc(100);
	//p is a second-level pointer, and *p means to dereference &str
}

void Test(void)
{
	char* str = NULL;
	GetMemory(&str);// The address of STR is passed
	strcpy(str, "hello world");
	printf(str);
	// The STR in parentheses corresponds to the beginning of the string
	free(str);// Free up space to prevent memory leaks
	str = NULL;
}

int main()
{
	Test();

	return 0;
}
Copy the code

Output result:


4.2 Topic 2:

What happens when you run the program?

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
char* GetMemory(void)
{
	char p[] = "hello world";
	return p;
}
void Test(void)
{
	char* str = NULL;
	str = GetMemory();
	printf(str);
}

int main()
{
	Test();

	return 0;
}
Copy the code

Output result:

Why is it such a strange result?

Since p in GetMemory creates a temporary local space to hold “Hello World”, STR returns the first character of “Hello World”, but the space created by P is automatically destroyed when the function is finished. When the content of the STR again want to visit the piece of space, the content was missing or be covered, for example, a popular point is equivalent to you to open a brand for 301 room, you call your friends to live, but your friend to come to the room 301 when you return had been made, and the boss has leased 301 to others, That is, the content of the original space is missing or overwritten.

This problem is also called the return stack address problem because the space created on the stack is automatically destroyed and reclaimed when it is out of range. The memory space allocation diagram is as follows:


4.3 Question 3:

What’s wrong with the program?

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void GetMemory(char** p, int num)
{
	*p = (char*)malloc(num);
}
void Test(void)
{
	char* str = NULL;
	GetMemory(&str, 100);
	strcpy(str, "hello");
	printf(str);
}

int main()
{
	Test();

	return 0;
}
Copy the code

It’s very simple. If you don’t have free space, we talked about it in the first problem, so you can jump back and watch it again.


4.4 Topic 4:

Can the program run successfully?

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void Test(void)
{
	char* str = (char*)malloc(100);
	strcpy(str, "hello");
	free(str);
	if(str ! = NULL) { strcpy(str,"world");
		printf(str);
	}
}

int main()
{
	Test();

	return 0;
}
Copy the code

The answer is no, because it’s called illegal access. Why is that?

First, we create 100 bytes of space in dynamic memory. Hello \0 is copied into STR, which is fine, but note that the free function returns 100 bytes of used space to the operating system after copying. However, it is no longer maintained by STR. When space is freed, STR attempts to use the space will be treated as illegal access, and the space will not be used by STR, so the final copy of world\0 will fail. So remember to null address (null pointer) after using the free function


5. C/C++ program memory development

As we have said before, memory is always created in the stack, heap and static areas of the three blocks, but this is a rough way to say, next we will have a more in-depth understanding of the rules of memory, first let’s look at a picture:

Combined with our above schematic map of memory area division, we can more clearly understand the rules of memory opening:

  1. Stack: During the execution of a function, storage units for local variables in the function can be created on the stack. These storage units are automatically released when the function is finished. Stack memory allocation operations are built into the processor’s instruction set, which is highly efficient, but the allocated memory capacity is limited. The stack area mainly stores the local variables, function parameters, return data, return address and so on allocated by the running function.
  2. Heap: Usually allocated and released by the programmer. If not released by the programmer, the program may be reclaimed by the OS at the end of the program. The allocation is similar to a linked list.
  3. Data segment (static area) (static area) stores global variables, static data. The program is released by the system after completion.
  4. Code snippet: Binary code that holds the body of a function (class member functions and global functions).

With this picture, we can better understand the static keyword modifying a local variable:

In fact, ordinary local variables are allocated space in the stack area, which is characterized by variables created there being destroyed out of scope. But static variables are stored in the data segment (the static section), which is characterized by variables created on it that are not destroyed until the end of the program, so the life cycle is longer.


6. Flexible arrays

You may never have heard of flexible arrays, but they exist. In C99, the last element in a structure is allowed to be an array of unknown size, which is called a “flexible array” member.

Such as:

typedef struct st_type1
{
	int a;
	int b[];// Flexible array member, size unknown
}type_a;
Copy the code

Error: failed to compile

typedef struct st_type2
{
	int a;
	int b[0];// Flexible array member, size unknown
}type_b;
Copy the code

6.1 Characteristics of flexible array:

  1. A flexible array member in a structure must be preceded by at least one other member;
  2. The size returned by sizeof does not include the memory of the flexible array;
  3. Structures containing flexible array members allocate memory dynamically using the malloc() function, and allocate more memory than the structure size to fit the expected size of the flexible array.

Such as:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
typedef struct S
{
	int a;
	int b[0];// A flexible array member
}s;

int main()
{  
	//sizeof(s) evaluates the sizeof an int, that is, the sizeof the member in front of the flexible array
	printf("sizeof(s)= %d\n", sizeof(s));

	return 0;
}
Copy the code

Output result:

6.2 Use of flexible arrays

When you use flexible arrays, you have to create a separate space for them

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
struct S
{
	int a;
	int arr[0];// Flexible array member, size unknown
};

int main()
{  
	// If I want to put 10 integers in my flexible array, I should open up memory like this
	struct S* pc = (struct S*)malloc(sizeof(struct S) + 10 * sizeof(int));
	Struct S* receives the address of the opened space using a pointer of the custom type struct S*
	pc->a = 10;// Assign a value to a
	// Then assign the flexible array arr
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		pc->arr[i] = i;// Assign a value to each member of arR
	}
	printf("%d\n", pc->a);// Print the value of a
	for (i = 0; i < 10; i++)
	{
		printf("%d ", pc->arr[i]);// Outputs the value of the flexible array arr
	}
	free(pc);// Free up the space created
	pc = NULL;// Prevent unauthorized access

	return 0;
}
Copy the code

Output result:

You can also use the Realloc function to create more space if you run out of space:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
struct S
{
	int a;
	int arr[0];// Flexible array member, size unknown
};

int main()
{
	// If I want to put 10 integers in my flexible array, I should open up memory like this
	struct S* ps = (struct S*)malloc(sizeof(struct S) + 10 * sizeof(int));
	Struct S* receives the address of the opened space using a pointer of the custom type struct S*
	ps->a = 10;// Assign a value to a
	// Then assign the flexible array arr
	int i = 0;
	struct S* ptr = (struct S*)realloc(ps, sizeof(struct S) + 20 * sizeof(int));
	// Create extra space as needed
	if(ptr ! = NULL) { ps = ptr;// If the address of the new space is not NULL, the address is assigned to ps as the starting address of the new space
	}
        for (i = 0; i < 10; i++)
        {
	        ps->arr[i] = i;
        }
	free(ps);// Free up the space created
	ps = NULL;// Prevent unauthorized access

	return 0;
}
Copy the code

Debugging result:

6.3 Advantages of flexible arrays

Let’s look at the following two ways:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
typedef struct st_type
{
	int i;
	int a[0];
}type_a;

int main()
{
	/ / code 1
	int i = 0;
	type_a* p = (type_a*)malloc(sizeof(type_a) + 100 * sizeof(int));
	// Business processing
	p->i = 100;
	for (i = 0; i < 100; i++)
	{
		p->a[i] = i;
	}
	free(p);

	return 0;
}
Copy the code

Method 2:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
typedef struct st_type
{
	int i;
	int* p_a;
}type_a;

int main()
{      
        / / code 2
	type_a* p = (type_a*)malloc(sizeof(type_a));
	p->i = 100;
	p->p_a = (int*)malloc(p->i * sizeof(int));
	// Business processing
	int i = 0;
	for (i = 0; i < 100; i++)
	{
		p->p_a[i] = i;
	}
	// Free up space
	free(p->p_a);
	p->p_a = NULL;
	free(p);
	p = NULL;


	return 0;
}
Copy the code

Code 1 and code 2 above do the same thing, but the implementation of method 1 has two advantages:

The first benefit is easy memory free

If our code is in a function for someone else to use, you do a secondary memory allocation in it and return the entire structure to the user. The user calls free to free the structure, but the user doesn’t know that members of the structure also need free, so you can’t expect the user to find out. So, if we allocate the structure’s memory and its members’ memory all at once, and return the user a pointer to the structure, the user can do a free to free all of the memory.

The second benefit is to increase access speed

Continuous memory is good for improving access speed and reducing memory fragmentation.


That’s the end of this post. If you think it’s helpful, please like it. See you next time!