[C] Detailed Explanation of Dynamic Memory Management

C/C++ memory development area

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 amount of allocated content 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).

insert image description here

Why does dynamic memory allocation exist?

The variables and arrays we usually create are all created on the stack, but the space they open up has two characteristics:

The space allocation size is fixed.
When the array is declared, the length of the array must be specified, and the memory it needs is 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 to open up space when the array is compiled cannot be satisfied. At this time, you can only try dynamic storage and development.

Introduction to dynamic memory functions (in the header file stdlib.h)

malloc

void* malloc (size_t size);

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.

free

It is specially used to release and recycle dynamic memory. The function prototype is as follows:

void free (void* ptr);

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.

Code demo:

#include <stdio.h>
#include <stdlib.h>

int main()
{
    
    
	int* p = (int*)malloc(40);
	if (p == NULL) //这里的判断是很有必要的
	{
    
    
		return;
	}
	for (int i = 0; i < 10; i++)
	{
    
    
		p[i] = 0 + i;
	}
	free(p);
	p = NULL; //防止出现野指针
	return 0;
}

calloc

void* calloc (size_t num, size_t size);

The calloc function is also used for dynamic memory allocation.

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.

Calloc initializes each byte to 0 when opening up space, while malloc only applies for space and does not initialize. Let's take an example:

#include <stdio.h>
#include <stdlib.h>
int main()
{
    
    
	int* pa = (int*)malloc(40);
	if (pa == NULL)
	{
    
    
		perror("malloc");
		return;
	}
	int i = 0;
	for (i = 0; i < 10; i++)
	{
    
    
		printf("%d\n", pa[i]);
	}
	free(pa);
	pa = NULL;
	int* pb = (int*)calloc(10, 4);
	if (pb == NULL)
	{
    
    
		perror("malloc");
		return;
	}
	for (i = 0; i < 10; i++)
	{
    
    
		printf("%d ", pb[i]);
	}
	free(pb);
	pb = NULL;
	return 0;
}

Let's take a look at the running results:
insert image description here
we can see that the space opened by malloc is not initialized, and the space opened by calloc is initialized to 0.

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.

Function prototype:

void* realloc (void* ptr, size_t size);
ptr is the memory address to be adjusted,
size, the new size after adjustment,
and the return value is the starting position of the memory after adjustment.
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

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.
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.

In view of the above two situations, we must pay attention to one point when using realloc. The starting address expanded by realloc cannot be directly paid to the original space. If the development fails, the original space will become uncontrollable, resulting in memory leaks. For example:

#include <stdio.h>
#include <stdlib.h>

int main()
{
    
    
	//开辟空间
	int* pa = (int*)malloc(40);
	//判断有效性
	if (pa == NULL)
	{
    
    
		perror("malloc");
		return;
	}
	//使用
	for (int i = 0; i < 10; i++)
	{
    
    
		pa[i] = i;
	}
	//扩容
	int* ptr = (int*)realloc(pa, 60);
	//判断是否开辟成功
	if (ptr == NULL)
	{
    
    
		perror("realloc");
		free(pa);
		pa = NULL;
		return;
	}
	//成功将起始地址给pa
	pa = ptr;
	//使用
	for (int i = 0; i < 15; i++)
	{
    
    
		printf("%d ", pa[i]);
	}
	//使用完后释放空间
	free(pa);
	pa == NULL;
	return 0;
}

All in all, we must judge the validity after opening the memory, otherwise there will be a dereference to the null pointer, causing the program to crash.

Common Dynamic Memory Mistakes

Dereference operation on NULL

For example:

void test()
{
    
    
	int* p = (int*)malloc(INT_MAX / 4);
	*p = 20;
	free(p);
}

Problems arise if p is NULL.

Out-of-bounds access to dynamically allocated space

For example:

void test()
{
    
    
	int* pa = (int*)malloc(40);
	//判断有效性
	if (pa == NULL)
	{
    
    
		perror("malloc");
		return;
	}
	//使用
	for (int i = 0; i <= 10; i++)
	{
    
    
		pa[i] = i;
	}
}

Here the problem arises when i = 10.

Free release the non-dynamically opened space

For example:

int main()
{
    
    
	int a = 0;
	int* pa = &a;
	free(pa);
	return 0;
}

This way of writing is also wrong. Free releases the dynamically opened space.

Use free to release part of the dynamically allocated memory

For example:

void test()
{
    
    
	int* p = (int*)malloc(40);
	p++;
	free(p);
}

At this point p no longer points to the starting position.

Multiple releases of the same dynamic memory

For example:

void test()
{
    
    
	int* p = (int*)malloc(40);
	free(p);
	free(p);
}

Here too it is very seriously wrong.

Dynamically allocated memory forgets to release

For example:

void test()
{
    
    
	int* p = (int*)malloc(100);
	if (NULL != p)
	{
    
    
		*p = 20;
	}
}


int main()
{
    
    
	test();
	while (1);
}

Forgetting to release dynamically allocated space that is no longer in use can cause memory leaks.
The dynamically opened space must be released and released correctly.

That's all for today's sharing, thank you for your attention and support.

Guess you like

Origin blog.csdn.net/bushibrnxiaohaij/article/details/131727714