The pointers provided by the C language allow us to directly manipulate the memory, bringing great flexibility while also bringing huge security risks. As the program grows, memory management becomes more difficult. Memory Management is an arduous task.
Memory problems caused by C language are caused by incorrect use and maintenance of memory. For example, C language beginners may write the following code:
char *p;strcpy(p, "hello world!");
Such a program will crash directly because the character pointer P is not initialized and Its stored value is unpredictable, generally, the address it points to is no longer used by our program, so the memory copy string directed to this pointer will naturally be rejected by the operating system, the reason is that unauthorized memory is used. Therefore, a valid memory must be allocated to P before replication.
The C language provides a function to directly apply for memory on the stack: void * malloc (size_t N). Be careful when using this function. First, check whether the allocation is successful, second, you must use the free function to release the memory when it is no longer used, but the difficulty is to determine the release time. Here, we use some specific examples to describe possible problems.
The first example is a common mistake for beginners. They may want to use a function to allocate memory for pointers and initialize them, so they can write such code:
int malloc_space(char *dest, int n){ if (n <= 0) return -1; dest = (char *)malloc(n * sizeof(char)); if (dest == NULL) return -2; memset(dest, 0, n); return 0;} int main(){ char *buffer = NULL; malloc_space(buffer, 1024); /* TODO: do something use buffer. */ return 0;}
However, this code will confuse them, because the program will always crash when using the buffer. Through tracking and debugging, we will find that after executing the malloc_space function, the pointer buffer value is still 0 (if you do not initialize it to null when defining the buffer, the buffer is a random address at this time, and it is more difficult to find the root cause of program errors ), this indicates that malloc_space is not able to allocate the buffer to the memory, but you will find that malloc_space is executed normally without errors, and the memory allocation is successful. In fact, the key issue here is that when a function is called, the form parameter will become a copy of the real parameter, so here you are actually
The memory allocated by the dest, instead of the buffer, is still null. There are two ways to solve the problem. One is to use the second-level pointer, that is, the pointer, and change the malloc_space function to this
int malloc_space(char **dest, int n){ if (n <= 0) return -1; *dest = (char *)malloc(n*sizeof(char)); if (*dest == NULL) return -2; memset(*dest, 0, n); return 0;}
You need to pass the pointer address to it when using it:
int i = 0;char *buffer = NULL;i = malloc_space(&buffer, 1024);if (i != 0){ /* Error:.... */} /* OK, do something use buffer. */
Another way is to allocate the memory to the malloc_space function and directly use the first address of the memory as the return value:
void *malloc_space(int n){ void *dest = NULL; if (n <= 0) return NULL; dest = malloc(n); if (dest == NULL) return NULL; memset(dest, 0, n); return dest;}
Then let the buffer accept its return value:
char *buffer = NULL;buffer = (char *)malloc_space(1024);if (buffer == NULL){ /* Error: no mmemory... */} /* OK, do something use buffer. */
Next, let's take a complete example: creating and destroying two-dimensional dynamic arrays, which is useful when processing matrices, because the C language must specify dimensions when defining arrays, but if you write a matrix multiplication function, you will not expect your program to be applicable only to matrices with fixed rows and columns. To implement this dynamic two-dimensional array, we first need to open up a one-dimensional array to store the first address of each row of the matrix, and 0th elements to store the first address of the first line of the matrix, the first address of the first line of the matrix containing 1st elements, and so on. Then assign a one-dimensional array for each element of the array to store each row of the matrix. Using the previous Implementation ideas, we can implement a function to complete this task:
int **create_array_2d(int row, int colume){ int **dest = NULL; if (row <= 0 || colume <= 0) return NULL; dest = (int **)malloc(row * sizeof(int *)); if (dest == NULL) return NULL; memset(dest, 0, row * sizeof(int *)); return dest;}
Now the pointer DEST has been divided into the space of a one-dimensional array, but each element is a pointer (int *). Now we need to divide each pointer into a one-dimensional array (the element is int) in order to store each row of the matrix, the create_array_2d function is transformed as follows:
int **create_array_2d(int row, int colume){ int **dest = NULL; int i = 0; if (row <= 0 || colume <= 0) return NULL; dest = (int **)malloc(row * sizeof(int *)); if (dest == NULL) return NULL; memset(dest, 0, row * sizeof(int *)); for (i = 0; i < row, i++) { dest[i] = (int *)malloc(colume * sizeof(int)); if (dest[i] == NULL) return NULL; memset(dest[i], 0, colume * sizeof(int)); } return dest;}
When the memory is successfully allocated each time, this function will allocate each element (int *) of the one-dimensional array DEST to the space of colume integers, therefore, it can accommodate row * colume integers. The most important thing is that it can use a [I] [J] to access the elements in the matrix, this seems that DEST is the same as the matrix itself, which is obviously helpful for code readability. However, there is an extremely serious problem here. In the for loop of the above function, allocating memory for each element (int *) of DeST may fail, if the memory is successfully allocated for Dest [1], Dest [2], and Dest [3 ],
Dest [4] failed to allocate memory. Obviously, the memory allocated by Dest [1], Dest [2], and Dest [3] should be released, however, a null pointer is returned and the result is a serious memory leak. Therefore, this function must be corrected as follows (note the nested for loop added in the for loop ):
int **create_array_2d(int row, int colume){ int **dest = NULL; int i = 0, j = 0; if (row <= 0 || colume <= 0) return NULL; dest = (int **)malloc(row * sizeof(int *)); if (dest == NULL) return NULL; memset(dest, 0, row * sizeof(int *)); for (i = 0; i < row, i++) { dest[i] = (int *)malloc(colume * sizeof(int)); if (dest[i] == NULL) { for (j = 0; j < i; j++) { free(dest[j]); dest[j] = NULL; } free(dest); dest = NULL; return NULL; } memset(a[i], 0, colume * sizeof(int)); } return dest;}
Note that it is best to develop some good habits. initialize the memory immediately after the memory is allocated successfully, and set the corresponding pointer to null immediately after the memory is released, to prevent the so-called "wild pointer" problem. Now we can create a matrix like this in the main function:
int rows = 10, columes = 6int **matrix = create_array_2d(rows, columes);if (matrix == NULL){ /* error: no memory... */}/* do something... */
After create_array_2d is successfully executed, you can assign a value to the two-dimensional array represented by Matrix: matrix [I] [J] = ..., after completing your task, we also need to release the two-dimensional array represented by matrix. Note that never release the array directly using the free (matrix) method, this only releases the space of the matrix one-dimensional array (the element is int *), but the space pointed to by each element is not released. The correct method is:
int destroy_array_2d(int ***a, int row, int colume){ int i = 0; if (row <= 0 || colume <= 0) return -1; for (i = 0; i < row; i++) { free((*a)[i]); (*a)[i] = NULL; } free(*a); *a = NULL; return 0;}
This code may be a bit difficult to read. I do not know whether the reader is still impressed with the failure to allocate memory to pointers through a function. If you want to change the value of the passed real parameter pointer, you must pass the pointer. Otherwise, it only changes the pointer of the form parameter. Therefore, we used the return value instead of the parameter method when allocating the memory, but now the release pointer must be a passing parameter. to modify the value of the second-level pointer (to be set to null), the third-level pointer must be passed. Of course, the cost is poor readability, but there is no way.