Today we continue to learn C language. Today's content is about dynamic memory. We know that memory can be divided into several plates, stack area, heap area and static area. Let me explain with a picture:
The variables we usually define in the main function are local variables. When we define a local variable, the memory will open up a space for us in the stack area. There is and only this space, which is also called static memory.
For another example, when we need to save a string of uncertain digits, how should you store it? We must first think of an array, but how large should this array be defined? Since we don't know how many numbers will be given, let's define 100 first? But what if only 10 numbers are given? Let's define 20? But what if there are 200 numbers? It seems that how to store it is inappropriate. Either it can't be saved or wasted. What should we do?
Next, we will lead to today's theme: dynamic memory development.
1. When is dynamic memory opened
Definition: dynamic memory, which enables users to specify Virtual operating system Start up the RAM capacity and maximize the system memory available to the platform.
What do you mean? In short, we can apply for the space we need from the memory. If we need one, we can apply for one. If we need 100, we can apply for 100 to minimize the waste. Then the process of computer developing memory for us is dynamic, so it is called dynamic memory development.
2. 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 way of opening up space has two characteristics: 1. The size of space opening up is fixed. 2. When the array is declared, 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 opening up space during array compilation can not be satisfied. At this time, you can only try dynamic storage development.
3.malloc
The first dynamic memory development function provided by C language is malloc. Let's see the explanation of its concept in MSDN.
Malloc only applies for one memory for us at a time. No matter your type is int or char, I will apply for whatever type you ask me to apply, but there is only one. Let's look at its usage: the formal parameter of malloc function is a size_t size represents the size of the development memory. The return value of malloc is a void *. We talked about the function pointer. We know that the return value of void * means that any type of pointer can be returned. Therefore, when we apply for int data memory, we return int * and char * if we apply for char. Let's take a look at the examples used by malloc function:
#include <stdlib.h> int* ret = (int*)malloc(sizeof(int));
If the development is successful, a pointer to the developed space is returned. 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.
We know that when a function is called and parameters are passed, local variables will be created in the called function, but when your function ends, the local variables will be destroyed and cannot be returned to the main function. However, when we use dynamic memory to open up a space, it will not be destroyed either in the main function or in our own interface function, Because we look back at the figure above, local variables and function parameters are opened up in the stack area, while dynamic memory is opened up in the heap area. They are not in the same memory space. Therefore, when calling a function to generate a stack frame, it will not affect the space opened up by malloc. At this time, someone will ask, if the function will not be destroyed after the end of the function, Isn't that when I use dynamic memory once, I have to eat a little of my memory? Yes, when we use dynamic memory development without destroying it, it will always be there. Although there are only a few bytes, this is obviously incorrect. How to destroy the memory developed by dynamic memory? C language provides another function: free. The free function is specially used to release the space of dynamic memory development, so it is not difficult to see that the dynamic memory development function and the free function are used together.
For example:
#include <stdio.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)//Determine whether the ptr pointer is null { int i = 0; for(i=0; i<num; i++) { *(ptr+i) = 0; } } free(ptr);//Freeing the dynamic memory pointed to by ptr ptr = NULL;//Is it necessary? //A: Yes, because when we release ptr, it points to a random memory //It is also called a wild pointer. In order not to cause other trouble, we choose to point it to NULL return 0; }
int main() { //int arr[10] = { 0 }; int* p = (int*)malloc(40); if (p == NULL) { return -1; } else { printf("Successful application"); for (int i = 0; i < 10; i++) { *(p + i) = i; } } free(p); p == NULL; ret
4.calloc
The second dynamic memory development function provided by C language is calloc. Let's look at the definition of calloc from MSDN as usual:
So, as a dynamic memory development function, what's the difference between it and malloc? Malloc - only applies for space in the heap area and returns the starting address without initializing the memory space; calloc - applies for space on the heap, initializes to 0, and returns the starting address. Let's try to understand with a piece of code:
int main() { int* p = (int*)calloc(10, sizeof(int)); if (p == NULL) { printf("%s", strerror(errno)); return -1; } for (int i = 0; i < 10; i++) { *(p + i) = i; } int* ptr = realloc(p, 20 * sizeof(int)); if (ptr != NULL) { p = ptr; } else { return -1; } for (int i = 10; i < 20; i++) { *(p + i) = i; } for (int i = 0; i < 20; i++) { printf("%d ", *(p + i)); } free(p); p = NULL; return 0; }
In fact, we can compare it with arrays. The difference is that arrays can only be local variables or global variables, which are opened in the stack area and static area respectively, while calloc is opened in the heap area.
5.realloc
The third dynamic memory development function provided by C language is realloc. The difference between realloc and the first two is that the first two open up an unused space for you to use, and realloc can increase the capacity of the original space. If the original space is 0, the function of realloc can also be equivalent to malloc.
ptr is the memory address size to be adjusted. The return value of the new size after adjustment is the starting position of the memory after adjustment. 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.
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. The realloc function can adjust the size of dynamic memory.
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
In case 1, to expand the memory, directly add space after the original memory, and the data in the original space will not change. 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. Due to the above two cases, we should pay attention to the use of realloc function.
#include <stdio.h> int main() { int *ptr = malloc(100); if(ptr != NULL) { //Business processing } else { exit(EXIT_FAILURE); } //Expansion capacity //Code 1 ptr = realloc(ptr, 1000);//Is this ok? (what happens if the application fails?) //Code 2 int*p = NULL; p = realloc(ptr, 1000); if(p != NULL) { ptr = p; } //Business processing free(ptr); return 0; }
After learning these two functions, let's look at some topics to deepen our understanding of dynamic memory development functions:
int main() { int* p = malloc(200);//Only 50 bytes can be accessed if (p == NULL) { return -1; } for (int i = 0; i < 80; i++)//Cross border visit { *(p + i) = i; } for (int i = 0; i < 80; i++) { printf("%d", *(p + i)); } free(p); p = NULL; return 0; }
int main() { int* p = (int*)malloc(20); if (p == NULL) { return -1; } *p = 0;//It is risky to write this directly. p may be a null pointer return 0; }
We know that when we use dynamic memory to open up a space, we need to use free to destroy it if it is not used. What if I don't destroy it? What are the consequences?
//Dynamic memory release //There are two ways to recycle the space applied for in the stacking area //1.free //2. When the program exits, the applied space will also be recycled automatically int main() { int* p = (int*)malloc(40); return 0; }
Let's do some classic written test questions to see what their investigation of dynamic memory functions is.
Topic 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 calling Test directly?
After calling Test directly, there will be a problem of wild pointer, because when your GetMemory function opens up a space for you, it does not return the p pointer. We know that when calling the function to pass parameters, a temporary copy of the arguments is created inside the function. It only completes the function and has no impact on the arguments in the main function. Therefore, str is not the space just opened up for you by malloc, So how do we change it?
//The first modification void GetMemory(char** p) { *p = (char*)malloc(100); } int main() { char* str = NULL; GetMemory(&str); strcpy(str, "helloworld"); printf(str);//The program hangs up //1. When STR is passed to p, the value is passed, and p is the temporary copy of str. therefore, when the starting address of the space opened by malloc is placed in p, STR will not be affected, and STR is still NULL //2. When str is NULL and strcpy wants to copy helloworld to the space pointed to by str, the program crashes because the space pointed to by NULL pointer cannot be accessed directly. return 0; }
//The second modification char* GetMemory(char* p) { *p = (char*)malloc(100); return p; } int main() { char* str = NULL; str = GetMemory(str); strcpy(str, "helloworld"); printf(str); return 0; }
Topic 2:
char *GetMemory(void) { char p[] = "hello world"; return p; } void Test(void) { char *str = NULL; str = GetMemory(); printf(str); }
The main problem with this problem is that we know that when calling a function, a stack frame will be opened in memory, and the variables created inside the function are the same. When this function is called, the stack frame will also be destroyed, so the malloc space does not exist, and the stack area will be destroyed. Therefore, the Getmemory function has been destroyed, the array created by p has also been destroyed, and the returned p is only a random value, If you want to solve this problem, remember to call the secondary pointer.
void GetMemory(char **p, int num) { *p = (char *)malloc(num); } void Test(void) { char *str = NULL; GetMemory(&str, 100); strcpy(str, "hello"); printf(str); }
The code here does not have the memory opened up by the free call, so a memory leak will occur.
void Test(void) { char *str = (char *) malloc(100); strcpy(str, "hello"); free(str); if(str != NULL) { strcpy(str, "world"); printf(str); } }
The memory opened by str has been free, and it is also necessary to copy and print the string, which is a typical cross-border access.
6. 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. Stack memory allocation 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 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 introduction to 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 characteristic of the data segment is that the variables created on it are not destroyed until the end of the program, so the life cycle becomes longer.
7. Flexible array
Next, we will introduce a new concept. Maybe 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 a "flexible array" member.
typedef struct st_type { int i; int a[0];//Flexible array member }type_a;
Characteristics of flexible array: the flexible array member in the 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.
//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
Use of flexible arrays:
struct st_type { int i; int a[];//Flexible array member }; //struct ts_type2 //{ // int i; // int a[0];// Flexible array member //}; int main() { //printf("%d", sizeof(struct st_tpye)); //The use of structures containing flexible array members should cooperate with the dynamic memory allocation function //struct st_tpye st;//error 4byte struct st_type* ps = (struct st_type*)malloc(sizeof(struct st_type) + 10 * sizeof(int)); if (ps == NULL) { printf("%s\n", strerror(errno)); return -1; } ps->i = 100; for (int i = 0; i < 10; i++) { ps->a[i] = i; } for (int i = 0; i < 10; i++) { printf("%d ", ps->a[i]); } //Not enough space, hope to adjust it to 20 struct st_type* ptr = (struct st_type*)realloc(ps, sizeof(struct st_type) + 20 * sizeof(int)); if (ptr == NULL) { printf("Expand space failed"); return -1; } else { ps = ptr; } free(ps); ps = NULL; return 0; }
struct st_type { int i; int a[];//Flexible array member }; //struct ts_type2 //{ // int i; // int a[0];// Flexible array member //}; int main() { struct st_type* ps = (struct st_type*)malloc(sizeof(struct st_type)); ps->i = 100; ps->a = (int*)malloc(10 * sizeof(int)); for (int i = 0; i < 10; i++) { ps->a[i] = i; } for (int i = 0; i < 10; i++) { printf("%d ", ps->a[i]); } //Capacity expansion int* ptr = realloc(ps->a, sizeof(int) * 20); if (ptr == NULL) { printf("Capacity expansion failed"); return; } else { ps->a = ptr; } free(ps->a); ps->a = NULL; free(ps); ps = NULL; return 0; }
Advantages of flexible arrays:
//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); //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 = 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 to facilitate memory release. If our code is in a function for others, you make 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 does not 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 that 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.)
Well, let's stop here about dynamic memory development. See you next time.