Advanced dynamic memory management of C language

Posted by shadow-x on Mon, 07 Mar 2022 14:05:41 +0100

Key points of this chapter

  • Why does dynamic memory allocation exist
  • Introduction to dynamic memory function

        malloc

        free

        calloc

        realloc

  • Common dynamic memory errors
  • Several classic written test questions
  • Flexible array

Foreword: the header files of these four dynamic memory functions are declared in stdlib H medium

The ability of free is limited to releasing the space opened by malloc calloc realloc

I Why is there dynamic memory allocation

The memory development methods we have mastered include:

int val = 20;//Open up four bytes in 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 size of the space is fixed.
  2. When declaring an array, you must specify the length of the array, 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, so the way of opening up space during array compilation cannot be satisfied. At this time, you can only try dynamic storage development.

First, let's take a look at the basic storage conditions of each memory area to understand the storage place of dynamic memory:

As can be seen from the figure, the dynamic memory is stored in the heap area

The variables opened in the stack area are automatically created and recycled. We don't need to release them. Only the space opened through the malloc calloc realloc function needs us to free

II Introduction to dynamic memory function

1. malloc and free

C language provides a function of dynamic memory development:

void* malloc (size_t size);
(size_t size unsigned integer is the size of the space to be opened, in bytes)

This function requests a continuously available space from memory and returns a pointer to this space.

  • If the development is successful, it returns a pointer to the developed space.
  • If the development 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 open space, which is determined by the user when using it.
  • If the parameter size is 0, malloc's behavior is standard and undefined, depending on the compiler.
  • After successful development, the space for successful development will not be initialized

The correct way to use this function to open up space:

int main()
{
	//Open up 10 integer spaces
	//int arr[10];
	int* p = (int*)malloc(40);//You can write large numbers to see the reasons for the failure
    //Judge whether the development is successful
	if (NULL == p)
	{
        //If the development fails, the reason for the printing failure
		printf("%s\n", strerror(errno));
		return 0;
	}
	//Only after successful development can it be used
    //initialization
	int  i = 0;
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i;
	}
    //use
	for (i = 0; i < 10; i++)
	{
		printf("%d ", p[i]);
	}
    free(p);//The ptr pointer will become the dynamic ptr pointer
    ptr = NULL;//Place null pointer
}

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

void free (void* ptr);

The free function is used to free the dynamic memory.

  • If the space pointed to by the parameter ptr is not dynamically opened up, the behavior of the free function is undefined.
  • If the parameter ptr is a NULL pointer, the function does nothing.

for instance:

#include <stdio.h>
#include <stdlib.h>
int main()
{
     //Code 1
     int num = 0;
     scanf("%d", &num);
     int arr[num] = {0};
     //Code 2
     int* ptr = NULL;
     ptr = (int*)malloc(num*sizeof(int));
     if(NULL != ptr)//Judge whether ptr pointer is null
     {
         int i = 0;
         for(i=0; i<num; i++)
         {
             *(ptr+i) = 0;
         }
     }
     free(ptr);//Release the dynamic memory pointed to by ptr, and then ptr will become a wild pointer
     ptr = NULL;//So this step is very necessary! Very important!
     return 0;
}

2. 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 is to open up a space for num size elements, and initialize each byte of the space to 0.
  • The difference between calloc and malloc is that calloc initializes each byte of the requested space to all zeros before returning the address.
  • The start address of the returned space after successful development, and NULL after failure. The return condition is the same as malloc
  • The usage is roughly the same as malloc, except for the first two points

for instance:

#include <stdio.h>
#include <stdlib.h>
int main()
{
     int *p = (int*)calloc(10, sizeof(int));
     if (NULL == p)
	 {
        //If the development fails, the reason for the printing failure
		printf("%s\n", strerror(errno));
		return 0;
	 }
    //No initialization is required and can be used directly
     for (i = 0; i < 10; i++)
	 {
		 printf("%d ", p[i]);
	 }
     //Free memory
     free(p);
     //Place null pointer
     p = NULL;
     return 0;
}

Therefore, how to initialize the contents of the applied memory space? It is convenient to use the calloc function to complete the task.

3. realloc 

  • The emergence of realloc function makes dynamic memory management more flexible.
  • Sometimes we find that the space applied for in the past is too small, and sometimes we feel that the space applied for is too large. In order to make the memory reasonable, we will flexibly adjust the size of the memory. Then realloc function can adjust the size of dynamic memory.
  • When the space adjustment fails, NULL is returned. The previous space is still there and has not been released

Note: if the first parameter passes NULL to realloc, the function is the same as malloc

The function prototype is as follows:

void* realloc (void* ptr, size_t size);
  • ptr is the memory address to be adjusted
  • New size after size adjustment (in bytes)
  • The return value is the adjusted starting position of memory.
  • This function will also move the data in the original memory to a new space on the basis of adjusting the size of the original memory space.
  • realloc adjusts memory space in two ways:

Case 1: there is enough space after the original space

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

Case 1

In case 1, to expand the memory, directly add space after the original memory, and the data in the original space will not change.

Situation 2

In case 2, when there is not enough space after the original space, the expansion method is to find another continuous space of appropriate size on the heap space. In this way, the function returns a new memory address. In case of case 2, when realloc finds another space, it will first copy the data of the original space to the new space, and then release the original space. We don't need to release it ourselves and return to the starting address of the newly opened memory

Due to the above two cases, we should pay attention to the use of realloc function.

For example:

#include <stdio.h>
int main()
{
     int *ptr = (int*)malloc(100);
     if(ptr != NULL)
     {
     //Business processing
     }
     else
     {
         exit(EXIT_FAILURE);    
     }
     //Expansion capacity


     //Code 1
     ptr = (int*)realloc(ptr, 1000);
     //This is risky. If the adjustment fails, NULL will be returned
 
     //Code 2
     int*p = NULL;
     p = realloc(ptr, 1000);
     //Judge that p is not a null pointer, and then give ptr
     if(p != NULL)
     {
         ptr = p;
     }
     //Business processing
     free(ptr);
     ptr = NULL;
     return 0;
}

III Common dynamic memory errors

1. Dereference of NULL pointer

void test()
{
     int *p = (int *)malloc(INT_MAX/4);
     *p = 20;//If the value of p is NULL, there will be a problem
     free(p);
}

For example:

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

int main()
{
	int* p = (int*)malloc(INT_MAX);//INT_MAX is the maximum value of int
	
    //Without this step, the program will crash directly
    if (p == NULL)
		return 0;

	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i;
	}

	return 0;
}

2. Cross border visits to dynamic open space

#include <stdlib.h>
void test()
{
     int i = 0;
     int *p = (int *)malloc(10*sizeof(int));
     if(NULL == p)
     {
         exit(EXIT_FAILURE);//Failure termination code
     }
     for(i=0; i<=10; i++)
     {
         *(p+i) = i;//Cross border access when i is 10
     }
     free(p);
     p = NULL;
}

3. Use free to release non dynamic memory

void test()
{
     int a = 10;
     int *p = &a;
     free(p);//Non dynamic space cannot be free
     p = NULL;
}

4. Use free to release a part of dynamic memory

void test()
{
 int *p = (int *)malloc(100);
 p++;
 free(p);//p no longer points to the starting position of dynamic memory
}

The following are easier to understand:

int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		return 0;
	}

	//Use memory
	int i = 0;
	//1~5
	for (i = 0; i < 5; i++)
	{
		*p = i+1;
		p++;
	}

	//release
	free(p);
	p = NULL;
	return 0;
}

As shown in the figure:

The space before P has not been released

 5. Multiple releases of the same dynamic memory

void test()
{
     int *p = (int *)malloc(100);
     free(p);
     /*p = NULL;But with this line of code, there will be no problem, and there will be no problem releasing null pointers*/
     free(p);//Release repeatedly. The same space can only be released once
}

6. Dynamic memory forgetting to release (memory leakage)

void test()
{
     int *p = (int *)malloc(100);
     if(NULL != p)
     {
         *p = 20;
     }
     //free(p);// The memory should be released here. If you forget to release it, there will be a memory leak
}

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

Or it can be released in this way:

int* test()
{
	int* p = (int*)malloc(100);
	if (p == NULL)
	{
		return 0;
	}

    return p;
}

int main()
{
	int* ptr = test();
	free(ptr);

	return 0;
}

Forgetting to release dynamically opened space that is no longer used can cause memory leaks.

Remember:

The dynamically opened space must be released and released correctly.

IV 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);
}
/*
Operation results:
Program crash

reason:
When STR is passed to the GetMemory() function, p is passed in the past. In fact, it is just a temporary copy of str
 In other words, although p points to the starting position of the 100 spaces opened up, but
 But after the program runs the GetMemory() function and returns, p has been destroyed, and str is actually
 I didn't get the starting position of the development space, that is, strcpy() originally wanted to "hello world"
Copy to the 100 byte space opened up, but it becomes copying "hello world" to NULL
 Doing so would have caused the program to crash. Not only that, but also the space for dynamic application was not released in the end
 Which in turn leads to memory leaks
*/

printf(str); This line of code should have no problem, because STR wants to point to h in "hello world" and print out the string

2.

char* GetMemory(void)
{
     char p[] = "hello world";
     return p;//Returned the address, which is a big taboo
}

void Test(void)
{
     char *str = NULL;
     str = GetMemory();//Illegal access
     printf(str);
}
//Running result: messy code such as hot

Return stack space address problem!

p is the address of the first element of the array, but after the function returns, the address of p has been recovered, and p has become a wild pointer. Therefore, after returning, the address obtained by str does not belong to us, but has been recovered

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);
     //free(str);
}
//No memory is released. Everything else is OK

4.

void Test(void)
{
     char *str = (char*) malloc(100);
     strcpy(str, "hello");
     free(str);
     //str = NULL;
     if(str != NULL)
     {
         strcpy(str, "world");
         printf(str);
     }
}
//The space pointed to by str has been released. Later, because str is not set to NULL, it leads to illegal access

V Memory development of C/C + + program

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

1. Stack: when executing a function, the storage units of local variables in the function can be created on the stack, and these storage units will be automatically released at the end of function execution. 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 the local variables, function parameters, return data, return address, etc. allocated for running the function.

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

3. The data segment (static area) stores global variables and static data. Released by the system at the end of the program.

4. Code segment: the binary code that stores the function body (class member function and global function).

With this picture, we can better understand the example of static keyword modifying local variables in the first knowledge of C language.

In fact, ordinary local variables allocate space in the stack area. The characteristic of the stack area is that the variables created above are destroyed when they leave the scope.

However, the variables modified by static are stored in the data segment (static area). The data segment is characterized by the variables created on it, which are not destroyed until the end of the program

So the life cycle becomes longer.

Vi Flexible array

Perhaps you have never heard of the concept of flexible array, but it does exist. In C99, the last element in the structure is allowed to be an array of unknown size, which is called "flexible array" member.

For example:

typedef struct st_type
{
     int i;
     int a[0];//Flexible array member
}type_a;

Some compilers will report errors and cannot compile. You can change it to:

typedef struct st_type
{
     int i;
     int a[];//Flexible array member
}type_a;

1. Features of flexible array:

  • A flexible array member in a structure must be preceded by at least one other member.
  • The size of this structure returned by sizeof does not include the memory of the flexible array.
  • The structure containing flexible array members uses malloc() function to dynamically allocate memory, and the allocated memory should be larger than the size of the structure to adapt to the expected size of the flexible array.

For example:

//code1
typedef struct st_type
{
     int i;
     int a[0];//Flexible array member
}type_a;
printf("%d\n", sizeof(type_a));//The output is 4

2. Use of flexible array

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

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

struct S2
{
	int n;
 int arr[];
};

int main()
{
	//printf("%d\n", sizeof(struct S1));
	struct S2* ps = (struct S2*)malloc(sizeof(struct S2) + 40);
	ps->n = 100;
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		ps->arr[i] = i;
	}
	//increase capacity
	struct S2* ptr = (struct S2*)realloc(ps, sizeof(struct S2)+80);
	if (ptr == NULL)
	{
		return 0;
	}
	else
	{
		ps = ptr;
	}
	//release
	free(ps);
	ps = NULL;

	return 0;
}

3. Advantages of flexible array

Type above_ A the structure can also be designed as:

//Code 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));
    //Business processing
    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;

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

The first advantage is: easy memory release

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

The second advantage is: This is conducive to access speed

Continuous memory is beneficial to improve access speed and reduce memory fragmentation. (in fact, I personally don't think it's much higher. Anyway, you can't run. You need to add the offset to address it.)

Extended reading:

Member array and pointer in C language structure | cool shell - CoolShell

Topics: C