15 C language advanced dynamic memory management

Posted by Negligence on Tue, 15 Feb 2022 06:50:38 +0100

Dynamic memory management

  • Significance of dynamic memory allocation

  • Introduction to dynamic memory function

    • malloc
    • free
    • calloc
    • realloc
  • Common dynamic memory errors

  • Several classic written test questions

  • Flexible array

Significance of dynamic memory allocation

We have pointed out the way of memory development:

//Open up four bytes of space
int a = 5;
//Open up ten spaces of one byte in a row
char arr[10] = {0};

However, the space opened up by these two methods 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.

However, the demand for space is not just the above situation. Sometimes we can't directly know how much space we need. We can only know when the program starts running or when the user uses it. At this time, the way of opening up space during compilation of arrays can't be satisfied.

At this time, you can try dynamic memory development.

Use of storage space

Introduction to dynamic memory function

malloc and free

The function of opening up memory provided by C language: malloc

void* malloc(size_t size);//Declaration of malloc function

Function function:

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

1. If the development is successful, return a pointer to the developed space.
2. If the development fails, a NULL Pointer, so use malloc´╝îTo check the return value.
3. The return value type is void\* ,Namely malloc The function does not know the type of open space. When it is used, it is forced to convert the pointer type.
4. If parameter size Is 0, malloc The behavior of the standard is undefined and depends on the compiler.

After opening up the space, you can't let it exist all the time, so the memory will always be stored. At this time, we also have a free function to free or reclaim the dynamically opened memory.

void free(void* ptr);

The free function is used to free the dynamic memory

1. If parameter ptr The space pointed to is not dynamically opened up, so free The behavior of a function is undefined.
2. If parameter ptr yes NULL Pointer, the function does nothing.

malloc and free are declared in the header file stdlib H medium.

Function usage example

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main(){
    //Request 10 integer spaces from memory
    int* p = (int *) malloc(10* sizeof(int ));//Change to int_ The max test can execute the if part
    if (p == NULL){
        //Reasons for printing errors
        printf("%s\n", strerror(errno));
    }
    else{
        //Normal use space
        int i = 0;
        for (i = 0; i < 10; ++i) {
            *(p+i) = i;
        }
        for (i = 0; i < 10; ++i) {
            printf("%d",*(p+i));
        }
    }
    //When the dynamically requested space is no longer used, it should be returned to the operating system.
    //Recycle space
    free(p);
    //After free is lost, p still points to the memory address.
    p = NULL;//It's safer.
    return 0;
}

calloc

In addition to malloc, C language also provides a function called calloc, which is also used for dynamic memory allocation.

void* calloc(size_t num,size_ 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 specific usage is the same as malloc and free.

int main() {
    int *p = (int *) calloc(10, sizeof(int));
    if (NULL != p) {
        //Use space
    }else{
        //Print error message
        printf("%s\n",strerror(errno));
    }
    free(p);
    p = NULL;
    return 0;
}

realloc

The realloc function makes dynamic memory management more flexible.

Sometimes when we think the memory request is small or large, we can use the realloc function to adjust it.

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

Parameter interpretation:

  • ptr is the memory address to be adjusted
  • Size is the new size when adjusting
  • The return value is the adjusted memory starting position
  • 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.

Precautions for realloc use:

  1. If there is enough space to append after the space pointed to by p, append directly and return p
  2. If reallop does not have enough memory space, it will find a new memory space in the old memory area. If reallop does not have enough memory space, it will return a new memory area. If reallop does not have enough memory space, it will find a new memory area.
  3. If append fails, null pointer will be returned It cannot be directly assigned to the original pointer to prevent data loss.

In memory condition:

int main() {
    int *p = (int *) malloc(20);
    if (p == NULL) {
        printf("%s\n", strerror(errno));
    } else {
        int i = 0;
        for (i = 0; i < 5; i++) {
            *(p + i) = i;
        }
    }
    int *ptr = realloc(p, 4000);
    if (ptr != NULL) {
        int i = 0;
        for (i = 5; i < 10; i++) {
            *(ptr + i) = i;
        }
        for (i = 0; i < 10; i++) {
            printf("%d\n", *(p + i));
        }
    }
    //Free up space 
    //--If there is not enough space, the original ptr space will be released in realloc
    free(ptr);
    ptr = NULL;
    return 0;
}

Common dynamic memory errors

Dereference operation on NULL pointer

Accessing a null pointer is illegal.

After dynamic memory allocation, you must judge the return value.

int main(){
    int *p = (int*)malloc(40);
    //In case malloc fails, p is assigned NULL
    *p = 0;
    free(p);
    return 0;
}

Use free to free up space for non dynamic memory

Program crash

int main() {
    int a = 10;
    int *p = &a;
    free(p);//OK?
}

Cross border access to dynamic open space

Program crash

int main() {
    int i = 0;
    int *p = (int *) malloc(10 * sizeof(int));
    if (NULL == p) {
        exit(EXIT_FAILURE);
    }
    for (i = 0; i <= 10; i++) {
        *(p + i) = i;//Cross border access when i is 10
    }
    free(p);
}

Use free to free up a part of the dynamic memory

When releasing, it must be released from the starting position of the applied space.

The program crashed.

Therefore, the pointer p of this starting address is generally not modified.

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

Multiple releases of the same dynamic memory

int main() {
    int *p = (int *) malloc(100);
    free(p);
    free(p);//Repeated release
}

Avoidance method: after free, set p to blank.

int main() {
    int *p = (int *) malloc(100);
    free(p);
    p = NULL;
    free(p);//Invalid release, the program will not crash.
}

Memory is not released after dynamic opening (memory leak)

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

In the following case, you can't release the test function if you want to release it.

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

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

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

Some languages have their own memory recycling mechanisms.

Errors in applying for dynamic memory or using dynamic memory will mostly cause the program to crash directly.

After free, remember to set the pointer to null.

Several classic written test questions

Topic 1

What is the execution result of the test function.

void GetMemory(char *p) {
    p = (char *) malloc(100);
}

void Test(void) {
    char *str = NULL;
    GetMemory(str);
    strcpy(str, "hello world");
    printf(str);
}
  1. Running code will crash
  2. There is a memory leak in the program

str is passed to p as a value. p is a formal parameter of the GetMemory function, which can only be valid inside the function. After the GetMemory function returns, the dynamic development memory has not been released and cannot be found, so it will leak memory.

void GetMemory(char *p) {
    p = (char *) malloc(100);//2. Memory not released
}

void Test(void) {
    char *str = NULL;
    GetMemory(str);//1. Here is the address of STR, not * str.
    strcpy(str, "hello world");//3. The program crashed here and illegally accessed memory
    printf(str);//There is no problem with this printf
}

int main(){
    Test();
    return 0;
}

Tip: str is a variable, which is passed to the function. It is a temporary copy of the variable when the function is running. If you want to operate on the variable, you must pass the address. However, since str itself is an address, the secondary pointer must be used in the function GetMemory to accept it.

This can work. It won't crash, but there is still a memory leak.

Modification method 2

Topic 2

What is the result of the following test function

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

void Test(void) {
    char *str = NULL;
    str = GetMemory();
    printf(str);
}
int main() {
    Test();
    return 0;
}

The program may crash, which may be random values. (supplement: local variables are stored in the stack area.)

Cause: the variable p created in GetMemory is destroyed after the function ends. What happens to the memory address returned by the function is unknown (a little illegal access).

Solution: first, because the variable in the function is destroyed after the function is completed, it is OK to extend the life cycle of the variable, that is, modify it with static when defining the variable in the function. (store variables in static area)

2, Use dynamic memory allocation to define the space, and then do not free in the function, so that the space of this address can still be accessed outside the function. But if it is written like this, there will be a memory leak. (store variables in heap)

Topic 3

What is the running result of the following test function

void GetMemory(char **p, int num) {
    *p = (char *) malloc(num);
}

void Test(void) {
    char *str = NULL;
    GetMemory(&str, 100);
    strcpy(str, "hello");
    printf(str);
}
int main() {
    Test();
    return 0;
}

The program can run and produce results, but the problem with the program is memory leakage. After using str, it does not free up space.

Modification error:

void Test(void) {
    char *str = NULL;
    GetMemory(&str, 100);
    strcpy(str, "hello");
    printf("%s", str);
    //Code modification
    free(str);
    str = NULL;
}

Topic 4

What is the running result of the following test function?

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

int main() {
    Test();
    return 0;
}

The expected results can be run, but the code still has problems. If you continue to use the address of the previously opened space after free, it is illegal access.

Note: free will not empty the content after freeing the space.

Modification:

void Test(void) {
    char *str = (char *) malloc(100);
    strcpy(str, "hello");
    //modify
    free(str);
    str = NULL;
    //if (str != NULL) {
    /    strcpy(str, "world");
        printf(str);
    }
}

int main() {
    Test();
    return 0;
}

Memory development of C program

(from video screenshot)

Memory allocation area:

  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 are 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 usually 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 snippet: the binary code that stores the function body (class member function and global function).

Through this figure, we can better understand the example of static keyword modifying local variables.

In fact, general 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 here are not destroyed until the end of the program.

So the life cycle becomes longer.

Flexible array

Flexible array: 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:

// struct S{
//     int n;
//     int arr[0];
// };
struct S{
    int n;
    int arr[];//Flexible array members of unknown size - the size of the array can be adjusted
};
int main(){
    struct S s;
}

Flexible array characteristics

  1. A flexible array member in a structure must be preceded by at least one other member
  2. The size of this structure returned by sizeof is memory that does not include flexible arrays.
  3. 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.
struct S{
    int n;
    int arr[];//Flexible array members of unknown size - the size of the array can be adjusted
};
int main(){
    struct S s;
    printf("%d\n", sizeof(s));//The result is 4
}

Use of flexible arrays

struct S{
    int n;
    int arr[];//Flexible array members of unknown size - the size of the array can be adjusted
};
int main(){
    //Create space
    struct S* ps = (struct S*)malloc(sizeof(struct S)+5*sizeof(int));
    ps->n=100;
    int i = 0;
    for (i = 0;i<5;i++){
        ps->arr[i] = i;
    }
    //There's not enough space. Continue to open up
    struct S* ps2 = realloc(ps,sizeof(struct S)+10*sizeof(int));
    if(ps2!=NULL){
        ps = ps2;
    }
    for (i = 5; i < 10; ++i) {
        ps->arr[i] = i;
    }
    for (i = 0; i < 10; ++i) {
        printf("%d ",ps->arr[i]);
    }
    free(ps);
    ps = NULL;
    return 0;
}

Advantages of flexible array

Implement the same functions as above, but do not use flexible arrays

struct S {
    int n;
    int *arr;//This pointer points to a dynamically opened space for storing arrays
};

int main() {
    //The applied space is the space of an int and a pointer
    struct S *ps = (struct S *) malloc(sizeof(struct S));
    ps->arr = malloc(5 * sizeof(int));
    int i = 0;
    for (i = 0; i < 5; i++) {
        ps->arr[i] = i;
    }
    for (i = 0; i < 5; ++i) {
        printf("%d ", ps->arr[i]);
    }
    // Resize
    int *ps2 = realloc(ps->arr, 10 * sizeof(int));
    if (ps2 != NULL) {
        ps->arr = ps2;
    }
    for (i = 5; i < 10; ++i) {
        ps->arr[i] = i;
    }
    for (i = 0; i < 10; ++i) {
        printf("%d ", ps->arr[i]);
    }
    //Free space z
    free(ps->arr);
    ps->arr = NULL;
    free(ps);
    ps = NULL;
    return 0;
}

In the above code, I think we can create PS as a structure variable and ps.arr as an array variable for dynamic application space.

Note: after the pointer variable allocates dynamic space, the variable it points to can be directly used as an array.

Compare the two implementations

The first advantage: convenient memory release

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

  2. Continuous memory is beneficial to improve access speed and reduce memory fragmentation.

The second benefit: it is conducive to access speed

Continuous memory is beneficial to improve access speed and reduce memory fragmentation.

Summary

The flexible array is the last element in the structure. (first there is a structure, then the last 0-length array), and this structure has at least two members.

In a structure, flexible array members do not specify the array size.

Purpose of using flexible arrays:

  1. If you want to manipulate a continuous space, it is used as an array.
  2. When memory is released, it only needs to be released once.

Topics: C Back-end