Play with dynamic memory management and program memory development——[C Language]

We have learned some methods of memory development before, such as using int float double, etc., as well as various types of arrays. These can open up memory space. But the spaces they open up are all dead, and they cannot be changed at will after opening, which is very inconvenient. Today we are going to learn some new ways to open up memory - dynamic memory development


 Table of contents

1. Why does dynamic memory allocation exist?

2. Introduction to dynamic memory functions

malloc and free 

 calloc function

 relloc function

3. Common dynamic memory errors

3.1 Dereference operation on NULL pointer

3.2 Out-of-bounds access to dynamically allocated spaces

3.3 Use free to release non-dynamic memory

3.4 Use free to release part of a dynamically allocated memory

3.5 Multiple releases of the same dynamic memory

 3.6 Dynamically open up memory and forget to release it (memory leak)

4. Memory allocation of C/C++ program 

5. Flexible array 

5.1 Features of flexible arrays:

5.2 Use of flexible arrays 

5.3 Advantages of flexible arrays


1. Why does dynamic memory allocation exist?

The memory development methods we have mastered are:

int val = 20;//Allow four bytes on the stack space

char arr[10] = {0};//Open up 10 bytes of continuous space on the stack space 

However, the above-mentioned way of opening up space has two characteristics:

1. The space allocation size is fixed.

2. When declaring an array, the length of the array must be specified, and the memory it needs will be allocated at compile time.

But the demand for space is not just the above situation. Sometimes the size of the space we need can only be known when the program is running, and the way of creating space when compiling the array is not enough. At this time, you can only try dynamic storage and development. The popular point is that you can develop as much as you want! ! !


2. Introduction to dynamic memory functions

malloc and free 

The C language provides a dynamic memory allocation function. Let's take a look at the prototype of the function first: 

This function applies for a contiguous available space from memory and returns a pointer to this space.

If the allocation is successful, a pointer to the allocated space is returned.

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

The type of the return value is void*, so the malloc function does not know the type of the opened space, and the user can decide by himself when using it.

If the parameter size is 0, the behavior of malloc is undefined by the standard and depends on the compiler. 

C language provides another function free, which is specially used to release and reclaim dynamic memory. The function prototype is as follows: 

The free function is used to release dynamically allocated memory.

If the space pointed to by the parameter ptr is not dynamically opened, the behavior of the free function is undefined.

If the parameter ptr is a NULL pointer, the function does nothing. 

Therefore, the space pointed to by ptr is generally a pointer opened by a dynamic memory function.

int main()
{
	int a = 10;
	int* p = &a;
	free(p);

	return 0;
}

The above is the wrong code, don't try it lightly, it will make the program crash! ! ! 

Both malloc and free are declared in the stdlib.h header file.

Let's go through a procedure:

int main()
{
	int arr[10];
	int* p = (int*)malloc(40);
	return 0;
}

When we want to apply for an integer array, we generally use int arr[10]; to define, but we can also use the malloc function to open up a 40-byte memory. We only need to give the byte size we want to apply for in the parameter. The malloc function we want should access four bytes at a time like an array, we can assign the address returned by malloc to the int* p pointer, but malloc returns a void* type pointer, we should cast it to int *.

We use malloc to mimic arrays for application:

int main()
{
	int num = 0;
	scanf("%d", &num);
	int* ptr = NULL;
	ptr = (int*)malloc(num * sizeof(int));
	if (NULL != ptr)//判断ptr指针是否为空
	{
		int i = 0;
		for (i = 0; i < num; i++)
		{
			printf("%d ", *(ptr + i));
		}
	}
	free(ptr);//释放ptr所指向的动态内存
	ptr = NULL;//是否有必要?
	return 0;
}

When we use the malloc function to open up space, we must judge whether the pointer is a null pointer, because malloc may fail to open up space!

When we have finished using the dynamic space, we must use the free function to release it, otherwise this memory will always exist unless the computer is turned off or the program ends! !

When we free the space, the ptr pointer is useless and becomes a wild pointer. So we must finally initialize the pointer. 

(These contents are still practical in the calloc and realloc functions, so we must pay attention)

If malloc is successfully opened, we can use the pointer to print out what is in the space:

 When we open up 20 spaces, we can access the content of 5 elements. All are random content, which can prove that the space opened up by the malloc function is not initialized. 

When we fail to develop, we can use the perror function to output the cause of the failure, so that we can better understand where the problem is. We can modify the module that we judge the pointer to be empty as follows:

int main()
		{
			int* ptr = NULL;
			ptr = (int*)malloc(INT_MAX);
			if (ptr == NULL)
			{
				perror("malloc");
				return 1;
			}
			else if (NULL != ptr)//判断ptr指针是否为空
			{
				int i = 0;
				for (i = 0; i < INT_MAX; i++)
				{
					printf("%d ", *(ptr + i));
				}
			}
			free(ptr);//释放ptr所指向的动态内存
			ptr = NULL;//是否有必要?
			return 0;
		}

When we write the creation byte to INT_MAX, there is not enough memory to open up, and a null pointer is returned. 

 calloc function

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

The function of the function is to open up a space for num elements whose size is size, and initialize each byte of the space to 0.

The only difference from the function malloc is that calloc will initialize each byte of the requested space to all 0s before returning the address. 

 Let's take an example:

#include <stdio.h>
#include <stdlib.h>
int main()
{
 int *p = (int*)calloc(10, sizeof(int));
 if(NULL != p)
 {
 //使用空间
}
 free(p);
 p = NULL;
 return 0;
}

Through the debugging of the program, we can see that all are initialized to 0, which is the difference from the malloc function.


 relloc function

What I just said to make memory allocation flexible is not reflected in the above two functions, but it will be fully reflected in the realloc function. Let me take a look at realloc.

The emergence of the realloc function makes dynamic memory management more flexible.

Sometimes we find that the space we applied for in the past is too small, and sometimes we feel that the space we applied for is too large. In order to make the memory reasonable, we must make flexible adjustments to the size of the memory. Then the realloc function can adjust the size of the dynamically allocated memory.

The function prototype is as follows: 

ptr is the memory address to adjust

size New size after adjustment

The return value is the adjusted memory starting position.

On the basis of adjusting the size of the original memory space, this function will also move the data in the original memory to the new space.

There are two situations when realloc adjusts the memory space:

Case 1: There is enough space after the original space

Case 2: There is not enough space after the original space

 Case 1: When it is case 1, if you want to expand the memory, you can directly add space after the original memory, and the data in the original space will not change.

Case 2: In case 2, if there is not enough space after the original space, the extension method is: find another continuous space of suitable size on the heap space for use. This function returns a new memory address. Due to the above two situations, some attention should be paid to the use of the realloc function. When realloc opens up a new space, it will copy the data in the old space to the new space and release the old space, and return the address of the new space

 for example:

#include <stdio.h>
int main()
{
 int *ptr = (int*)malloc(100);
 if(ptr != NULL)
 {
     //业务处理
 }
 else
 {
     exit(EXIT_FAILURE);    
 }
 //扩展容量
 //代码1
 ptr = (int*)realloc(ptr, 1000);//这样可以吗?(如果申请失败会如何?)

 //代码2
 int*p = NULL;
 p = realloc(ptr, 1000);
 if(p != NULL)
 {
 ptr = p;
 }
 //业务处理
 free(ptr);
 return 0;
}

When we use malloc to open up space is not enough, we will use realloc to increase the space. This should be seen in two cases. When the original space is large enough, you can directly add extra space behind and return to the original address; if the space is not enough, a new large enough space will be opened and the address of the new space will be returned. If you use code 1 to operate and always use the same pointer variable to maintain, it doesn’t matter if the opening is successful, but if the opening fails, realloc will return a NULL to ptr, then the variable created with malloc before has no pointer to find, resulting in data Lost, and there is no way to release the space opened by malloc, it will cause serious consequences! ! ! So we should create a new pointer variable to receive the pointer returned by realloc (written in code 2)!

When we use the above three functions, we must pay attention to the points just emphasized to prevent bugs!


3. Common dynamic memory errors

3.1 Dereference operation on NULL pointer

The first type of problem is the most common mistake when we use the dynamic memory function, that is, not to judge the return value of the function (whether it is NULL), so as to directly dereference the receiving pointer. The problem arises when dereferencing a null pointer:

 void test()
{
 int *p = (int *)malloc(LLONG_MAX);
 *p = 20;//如果p的值是NULL,就会有问题
 free(p);
}

The above code will definitely fail to open because there is not enough space, thus giving p=NULL; so dereferencing p will cause an error, so we must judge the received pointer when using dynamic memory development! ! !

3.2 Out-of-bounds access to dynamically allocated spaces

We use a program to understand this problem:

void test()
{
 int i = 0;
 int *p = (int *)malloc(10*sizeof(int));
 if(NULL == p)
 {
 exit(EXIT_FAILURE);
 }
 for(i=0; i<=10; i++)
 {
 *(p+i) = i;//当i是10的时候越界访问
 }
 free(p);
}

When we use malloc to open up 40 bytes of space, when using the for loop to access the value in the space, when the loop i=10, the access has been out of bounds. Assuming that i=10 is accessed, it is equivalent to accessing 44 bytes of space, which exceeds the open space. This is similar to an array. If an out-of-bounds access may modify other contents of the heap area, this is very dangerous! ! !


3.3 Use free to release non-dynamic memory

This I have already mentioned in the malloc module to free the dynamically allocated memory, which will cause the program to crash directly!

void test()
{
 int a = 10;
 int *p = &a;
 free(p);//ok?
}


3.4 Use free to release part of a dynamically allocated memory

When using the pointer returned by the dynamic memory function, when we move the pointer, the position pointed to by the pointer after use is no longer the first address of the opened memory. If we directly use this pointer to free release, we will release it Part of dynamic memory development!

void test()
{
 int *p = (int *)malloc(100);
 p++;
 free(p);//p不再指向动态内存的起始位置
}

At this time, the program will crash, so when we use this pointer, it is best to create another pointer variable to operate to avoid this error. 


3.5 Multiple releases of the same dynamic memory

 This problem generally only occurs when the programmer is confused, and free releases the same piece of open space multiple times.

int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		return 1;
	}
	free(p);
	free(p);
		return 0;
}

This program will also crash! ! !


 3.6 Dynamically open up memory and forget to release it (memory leak)

If you forget to release the dynamically allocated memory space, it will cause a memory leak! ! !

Below I will give a more extreme program:

void test()
{
 int *p = (int *)malloc(100);
 if(NULL != p)
 {
 *p = 20;
 }
}
int main()
{
 test();
 while(1);
return 0;
}

When we call the function again, we use the malloc function to open up a memory space and use the p pointer to receive the return value. After using it, we forget to release it, but the pointer p will be destroyed after the function ends, but the memory block created by malloc will not. After this function, there is no pointer pointing to this piece of memory, and it will never be found again (just like the undercover policeman in the police movie, only Chen sir knows, but one day Chen sir dies, and no one will know the identity of the undercover policeman anymore ), and then use while(1) to enter an infinite loop so that the program will never end and this piece of memory will leak! ! !

Forgetting to release dynamically allocated space that is no longer in use can cause memory leaks.

Remember: The dynamically allocated space must be released and released correctly 


4. Memory allocation of C/C++ program 

Here is a picture for everyone, we can clearly see the memory allocation:

Several areas of C/C++ program memory allocation:

1. Stack area (stack): When a function is executed, the storage units of local variables in the function can be created on the stack, and these storage units are automatically released when the function execution ends. The stack memory allocation operation is built into the instruction set of the processor, which is very efficient, but the allocated memory capacity is limited. The stack area mainly stores local variables allocated by running functions, function parameters, return data, return address, etc.

2. Heap area (heap): Generally, it is allocated and released by the programmer. If the programmer does not release it, it may be reclaimed by the OS at the end of the program. The allocation method is similar to a linked list.

3. The data segment (static area) (static) stores global variables and static data. Released by the system after the program ends.

4. Code segment: store the binary code of the function body (class member functions and global functions).


5. Flexible array 

Maybe you've never heard of the concept of flexible arrays, but they do 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.

For example:

typedef struct st_type
{
 int i;
 int a[0];//柔性数组成员
}type_a;

Some compilers will report errors that cannot be compiled, which can be changed to: 

typedef struct st_type
{
 int i;
 int a[];//柔性数组成员
}type_a;

The number of elements of the array may be given as 0 or not given.

5.1 Features of flexible arrays:

A flexible array member in a structure must be preceded by at least one other member.

The size of such a structure returned by sizeof does not include memory for flexible arrays.

Structures containing flexible array members use the malloc() function for dynamic allocation of memory, and the allocated memory should be larger than the size of the structure to accommodate the expected size of the flexible array.

typedef struct st_type
{
 int i;
 int a[0];//柔性数组成员
}type_a;
printf("%d\n", sizeof(type_a));//输出的是4

When we calculate the size of this structure, although the member has an integer variable i and an array of int type, but its array is a flexible array (the number of elements is 0), we can understand that it has no size. So the size of this structure is 4.

5.2 Use of flexible arrays 

//代码1
int i = 0;
type_a *p = (type_a*)malloc(sizeof(type_a)+100*sizeof(int));
//业务处理
p->i = 100;
for(i=0; i<100; i++)
{
 p->a[i] = i;
}
free(p);

Dynamically open up space for structures with flexible arrays, because sizeof(type_a) can only find the size of non-flexible arrays, and the size of the array we want to open up is the size we open up with malloc, and then use The structure pointer can be received to get the space memory we want. In this way, the flexible array member a is equivalent to obtaining a continuous space of 100 integer elements. 


5.3 Advantages of flexible arrays

//代码2
typedef struct st_type
{
 int i;
int *p_a;
}type_a;
type_a *p = (type_a *)malloc(sizeof(type_a));
p->i = 100;
p->p_a = (int *)malloc(p->i*sizeof(int));
//业务处理
for(i=0; i<100; i++)
{
 p->p_a[i] = i;
}
//释放空间
free(p->p_a);
p->p_a = NULL;
free(p);
p = NULL;

 The above code 1 and code 2 can accomplish the same function, but the implementation of method 1 has two advantages:

The first benefit is: easy memory release

If our code is in a function for others, you do a secondary memory allocation in it and return the entire structure to the user. The user can release the structure by calling free, but the user does not know that the members of this structure also need to be free, so you can't expect the user to find this out. Therefore, if we allocate the memory of the structure and the memory required by its members at one time, and return a pointer to the structure to the user, the user can release all the memory by doing a free once.

The second advantage is: this is conducive to access speed

Contiguous memory is beneficial to improve access speed and reduce memory fragmentation. 

 The above two codes put all the allocated memory in the heap area, in order to reduce the difference and achieve a more similar purpose. In fact, there is no need for this. In code 2, just open up a space for the pointer object in the structure.

The above is the whole content of this issue. I hope you guys can point out my shortcomings in the comment area. Learning with humility is my belief! ! !

Guess you like

Origin blog.csdn.net/m0_74755811/article/details/131820896