Dynamic memory management: teach you to create variables on the heap to avoid the destruction of the stack

Before learning malloc, the storage of all the data we developed was basically stored on the stack.

int val = 20;//在栈空间上开辟四个字节
char arr[10] = {0};//在栈空间上开辟10个字节的连续空间

The above method of opening up space is still very classic, but in fact, it is still uncomfortable for us to use. Sometimes when we deal with some problems, we cannot know in advance how big my array needs to be, and the C language itself does not support variable-length arrays. Therefore, it is difficult to make the size of the array adaptive, and it is usually necessary to change it manually.

Another very uncomfortable thing about the data stored on the stack is that it is destroyed when the function is exited.

All in all...it's pretty annoying.


If you still don't know anything about memory allocation in C language, here is some overview.

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



Therefore, the development of dynamic memory is still very useful and convenient, and the following are some descriptions of dynamic memory development.

Trailblazer: malloc

malloc and free

 The C language provides a dynamic memory allocation function:

void* malloc (size_t size);

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

The location of this opened space is different from our normal opened space, it is opened on the heap.

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.

int main ()
{
    int* ret = (int*)malloc(sizeof(int) * 4);
    if(ret == NULL )
    {
        perro("malloc fail!");
        exit(-1);
    }
    return 0;
}

 Returning a null pointer is very dangerous, we use the above code to prevent this from happening.
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
.

int* ret = (int*)malloc(sizeof(int) * 4);

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

But since the space opened up on the heap avoids the blow from the destruction of the function stack frame, then the release of the memory we opened up has to be done by ourselves. After all, it is equivalent to automatic manual transfer, and the opened up space is not returned to others I can't justify it.

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

free

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.
Both malloc and free are declared in the stdlib.h header file.

 

int main ()
{
    int* ret = (int*)malloc(sizeof(int) * 4);
    if(ret == NULL )
    {
        perro("malloc fail!");
        exit(-1);
    }
    free(ret);
    ret = NULL;
    return 0;
}

After we free the space from malloc, don't forget to empty the pointer used to store the opened space. This is a good habit.

What happens if we don't release?

Forgetting to release the dynamically allocated space that is no longer used will cause a memory leak!

The problem of memory leaks is very serious, because once a memory leak is caused, we will no longer be able to make any changes to that space, and there is no way to find that space anymore. If this happens too much, the entire program will crash, so you need to be very careful.

calloc

C language 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);

 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.
for example:

#include <stdio.h>
#include <stdlib.h>
int main()
{
    int *p = (int*)calloc(10, sizeof(int));
    if(NULL != p)
    {
        perro("malloc fail!");
        exit(-1);
    }
    free(p);
    p = NULL;
    return 0;
}

 We get the address of p and monitor the memory.


So, how to choose between calloc and malloc

Use calloc when initialization is required and malloc when not

The two functions mentioned just now can actually open up space dynamically. We only need to pass variables as parameters when passing parameters, but the next one is even more important, and its business scope is wider. .

realoc

 Used to adjust the size of the allocated memory space

Although we can dynamically open up the size of the memory space by controlling the size of the variable, it still happens that the size of the memory space is enlarged or reduced during the running of the program, and realoc can handle this situation very well.

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

ptr is the memory address
size to be adjusted, 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 in which this function is adjusted:

Case 1: There is enough space after the original space

When the memory to be expanded can be accommodated, realoc will expand directly behind the memory to be expanded

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

In order to avoid out-of-bounds access when expanding the memory, realoc actually finds a place where the required size can be stored to create a new space, copy the space that originally needs to be expanded, and the original expanded old space will be released by realoc Lose

 

 Due to the above two situations, some attention should be paid to the use of the realloc function

For example, the following situation:

int main()
{
	int* ptr = (int*)malloc(100);
	if (ptr != NULL)
	{
		//业务处理
	}
	else
	{
		exit(EXIT_FAILURE);
	}

	//扩展容量
	//代码1
	ptr = (int*)realloc(ptr, 1000);//这样可以吗?(如果申请失败会如何?)
}

We may feel right at first glance. We use realoc to adjust the size of ptr to open up space, and then update ptr back. It seems that there is no problem at all?

In this case, if our ptr fails to open up space, what will happen if so much data cannot be opened?

We know that when the space development fails, a null pointer will be returned, and the trouble of the null pointer will be a big problem, so the next assignment, send it! The value of ptr is lost!

So we have to change it like this:

int* newptr = ptr;
newptr = (int*)realloc(ptr, 1000);
//用一个新指针去接收。

if(newptr == NULL)
{
    newptr = ptr;
}

If it is empty, at least the information pointed to by ptr will not be lost

Common Dynamic Memory Errors

 It is still necessary to be very careful when operating on the heap. If you do not pay attention, it may cause memory leaks. Some common mistakes are summarized here.

Dereference operation on NULL pointer:

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

It seems that there is no problem, but the return value after opening is not checked. If *p fails to open at this time and is a null pointer, it will be very troublesome.

Out-of-bounds access to dynamically allocated space:

void test()
{
    int i = 0;
    int *p = (int *)malloc(10*sizeof(int));

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

    free(p);
}

The classic miscounting problem.

Use free to release non-dynamically allocated memory:

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

 It collapsed directly.

Use free to release part of a dynamically allocated memory:

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

One less int-sized space is released, causing memory leaks

The address of the first element of the array: what about me? Are you leaving me alone?

 Multiple releases of the same dynamic memory

void test()
{
    int *p = (int *)malloc(100);
    free(p);
    free(p);//重复释放
}

This is completely unnecessary, and it's broken.

However, the free function does not act on NULL, so if p is empty, this program is fine.

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

This issue has already been mentioned before, so I won't describe it too much.

Several classic written test questions

 1.

void GetMemory(char *p)
{
    p = (char *)malloc(100);
}
void Test(void)
{
    char *str = NULL;
    GetMemory(str);
    strcpy(str, "hello world");
    printf(str);
}

What is the result of running the Test function?

 collapsed.

A very classic mistake is made here, that is, the problem of the destruction of the function stack frame. It is true that we can avoid the blow of space destruction by opening up space on the heap, but p itself is still a formal parameter inside Getmemory, which is just the address of str The copy of the function will be destroyed immediately after exiting the function, so there is nothing left.

If you want to use functions to open up space, you can fix it like this:

 Although it's not really necessary hahahahahaha.


2.

char *GetMemory(void)
{
    char p[] = "hello world";
    return p;
}
void Test(void)
{
    char *str = NULL;
    str = GetMemory();
    printf(str);
}

What will this code output?

 It is still a very classic problem. The char p【】 array has been destroyed after the function is executed and returned to the operating system. Even if str can find the original address, it is not hello world stored in it.


3.

void GetMemory(char **p, int num)
{
    *p = (char *)malloc(num);
}
void Test(void)
{
    char *str = NULL;
    GetMemory(&str, 100);
    strcpy(str, "hello");
    printf(str);
}

What will this program output?

 This actually belongs to the logic of our normal parameter passing. Since passing its actual parameter does not work, let’s pass its pointer. This is feasible, but it still misses a release here, and we will make it up for him.


void Test(void)
{
    char *str = (char *) malloc(100);

    strcpy(str, "hello");

    free(str);

    if(str != NULL)
    {
        strcpy(str, "world");
        printf(str);
    }
}

What will this program print?

Free release after copying, malloc space has been reclaimed, but the str pointer still remembers the starting address, and it also copies the space using str, illegal operation, resulting in wild pointers

So when we finish free, we should immediately set the pointer to NULL.

flexible array

 In C99, the last element in a structure is allowed to be an array of unknown size, which is called a "flexible array" member.

struct S3
{
	int num;//4
	int arr[];//柔性数组成员
};
 
int main()
{
	printf("%d\n", sizeof(struct S3));//?
	return 0;
}

Flexible arrays must use dynamic memory to use

We promised the size of the entire structure and got 4 bytes.

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.

struct S3
{
	int num;//4
	int arr[];//柔性数组成员
};
 
int main()
{
	struct S3* ps = (struct S3*)malloc(sizeof(struct S3)+40);

	return 0;
}

Although the flexible array itself does not take up any space like a ghost, we still need to add space for it when allocating space. In fact, it does not take up space itself, which is convenient for us to use, so that when the whole structure is complex, we don't need to calculate the size of the array specially.

//代码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);

In this way, the flexible array member a is equivalent to obtaining a continuous space of 100 integer elements.

But we may think, it’s quite painful to use this way, can’t we directly create a pointer inside the structure and malloc it out as an array?

For example like this:

typedef struct st_type
{
    int i;
    int* pa;
}type_a;


int main()
{
    type_a* p = (type_a*)malloc(sizeof(type_a));
    p->i = 100;
    p->pa = (int*)malloc(p->i * sizeof(int));
    //业务处理
    for (int i = 0; i < 100; i++)
    {
        p->pa[i] = i;
    }
    //释放空间
    free(p->pa);
    p->pa = NULL;
    free(p);
    p = NULL;
}

This is also feasible, but we need to consider that when we program for users, the advantages of flexible arrays are reflected

Release is simple:

For the structure of the flexible array, it is quite convenient for us to use, and it is also very convenient when releasing it. Just free the entire structure pointer without considering other things.

But using pointers is a pain in the ass because I not only need to release the structure pointer, but also need to release the space of that pointer.

This is very uncomfortable. You can't expect users to fully understand the underlying structure of your implementation. Let users not only release the structure, but also need to release the member pointers. This is not acceptable.

Of course, flexible arrays can also improve certain access efficiency:

Contiguous memory is good for improving access speed and reducing memory fragmentation (improving memory utilization). The CPU fetches the data in the register. According to the principle of locality, when the data is accessed, the surrounding data will be loaded into the register. When the subsequent data is accessed, the hit probability in the register will be higher when the CPU accesses the data.

If no data is found in the register, the CPU will go to the cache -> memory fetch until the corresponding data is found


Thanks for reading! Hope to help you a little bit!

 

Guess you like

Origin blog.csdn.net/m0_53607711/article/details/126886028