<C language> Dynamic memory management

1. Dynamic memory function

Why does dynamic memory allocation exist?

int main(){
    
    
    int num = 10;  //向栈空间申请4个字节
    int arr[10];   //向栈空间申请了40个字节
    return 0;
}

The above method of opening up space has two characteristics:

  1. The space allocation size is fixed.
  2. When the array is declared, the length of the array must be specified, and the memory it needs is 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 to open up space when the array is compiled cannot be satisfied. At this time, dynamic memory development is required.

1.1 malloc and free

malloc()It is used to dynamically allocate memory during program execution. Its full name is "memory allocation", which means memory allocation. malloc()Functions are part of the C standard library and their declarations are in stdlib.hheader files.

The function prototype is as follows:

void* malloc(size_t size);

Here, sizeis the number of bytes you want to allocate, and the function returns a pointer to the start address of the allocated memory block. malloc()The return type of the function is void*, which means that the returned pointer can be assigned to any pointer type without explicit conversion.

Here's a brief explanation malloc()of how it works:

1. You provide the number of bytes you want to allocate, and malloc()search the heap for a contiguous block of memory large enough to store those bytes.

2. If it finds a suitable memory block, it marks it as used and returns a pointer to the starting address of the memory block.

3. If it cannot find a large enough memory block, it will return a NULLpointer indicating that the memory allocation failed.

Note : Using malloc()the allocated memory needs to be free()explicitly released using the function, otherwise it will cause a memory leak.

void free(void* ptr);

free()The function takes a pointer to a previously allocated block of memory and frees it, making it available for future dynamic allocations. If you forget to free previously allocated memory, your program will consume more memory each time it runs the allocation code and may eventually run out of memory.

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

int main() {
    
    
    int n = 5;
    int* dynamicArray = (int*)malloc(n * sizeof(int));

    if (dynamicArray == NULL) {
    
    
        printf("内存分配失败!\n");
    } else {
    
    
        // 使用分配的内存块
        for (int i = 0; i < n; i++) {
    
    
            dynamicArray[i] = i + 1;
        }

        // 当不再需要分配的内存时,记得释放它
        free(dynamicArray);
        dynamicArray = NULL}

    return 0;
}

It 's good practice, but not required, to set the pointer after calling free()a function to free dynamically allocated memory .dynamicArrayNULL

Advantages of setting pointers to NULL:

  1. Avoid dangling pointers (Dangling Pointer): If the pointer is not set to after freeing the memory NULL, the pointer will still retain the previous address. If you continue to use this pointer in subsequent code, it may cause a dangling pointer, that is, the memory pointed to by the pointer has been freed, which may cause the program to crash or produce errors that are difficult to debug. Setting the pointer to NULLcan help you avoid this, because the program will produce an explicit error (null pointer dereference) if you try to use a null pointer.
  2. Avoid double-freeing: After freeing memory, if you set the pointer to NULL, you can NULLdetermine whether the memory has been freed by checking whether the pointer is . If you mistakenly call it again in subsequent code free(), it will result in undefined behavior.

If you are careful to avoid dangling pointers and repeatedly freeing memory in subsequent code, then not setting to NULLwill not cause problems either. However, this is a simple and helpful extra safeguard against bugs, so it is recommended to set the pointer to after freeing the memory NULL.

1.2 calloc

calloc()is another dynamic memory allocation function that also belongs to the standard C library (stdlib.h header file). Similar to malloc()function, but with some differences in usage.

calloc()The prototype of the function is as follows:

void* calloc (size_t num, size_t size);

where numis the number of elements you want to allocate and sizeis the size in bytes of each element. calloc()The function allocates space for num * sizea memory block of bytes and initializes all bits in that memory block to zero.

One advantage over malloc(), calloc()is that it automatically initializes the allocated memory, which means you don't need to manually zero out the allocated memory. In some cases, this can be very useful, especially when you need to ensure that allocated memory is zero to begin with.

Example:

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

int main() {
    
    
    int n = 5;
    int* dynamicArray = (int*)calloc(n, sizeof(int));

    if (dynamicArray == NULL) {
    
    
        printf("内存分配失败!\n");
    } else {
    
    
        // 使用分配的内存块,这里的内存已经被初始化为零
        for (int i = 0; i < n; i++) {
    
    
            printf("%d ", dynamicArray[i]); // 输出: 0 0 0 0 0
        }

        // 当不再需要分配的内存时,记得释放它
        free(dynamicArray);
    }

    return 0;
}

Summarize:

calloc = malloc+memset initialized to 0

1.3 realloc

reallocis a function used to reallocate the size of a memory block. Specifically, it can be used to change the size of a previously passed mallocor callocallocated memory block.

reallocThe declaration of the function is as follows:

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

Parameter Description:

  • ptr: A pointer to a previously allocated memory block. If ptrit is NULL, reallocthe behavior is equivalent mallocto allocating a new memory block.
  • size: The new memory block size, in bytes.

reallocworks like this:

  1. If ptrNULL, then reallocthe behavior is equivalent to that malloc(size)it will allocate a new sizememory block of size bytes and return a pointer to the memory block.
  2. If sizeit is 0 ptrand not NULL, then reallocthe behavior is equivalent free(ptr)to releasing the previously allocated memory block and returning a NULL pointer.
  3. If ptrnot NULL sizeand not 0, reallocan attempt will be made to reallocate previously allocated memory blocks. Several things can happen:
    • If the size of the previously allocated memory block is greater than or equal to that size, no new memory block will be allocated, but a pointer to the original memory block will simply be returned without changing the contents of the original memory block.
    • If the size of the previously allocated memory block is smaller than that size, reallocan attempt will be made to extend the original memory block to the new size. This may **extend in the available memory space behind the original memory block, and if there is not enough contiguous space to expand, it may reallocreallocate a new memory block in another place and copy the original content to the new memory block. **This means that reallocit is possible to return a new pointer instead of the original pointer, so after using it realloc, it is better to assign the returned pointer to the original pointer.
    • If reallocallocation of a new memory block fails, NULL is returned, and the previously allocated memory block remains unchanged.

When using realloc, special attention should be paid to the following points:

  • If reallocit returns NULL, it means that the reallocation failed, and the original pointer is still valid. To avoid memory leaks, the original pointer should be saved, and the previous memory block should be released as needed.
  • When used realloc, it is best not to modify the raw pointer directly, but to reallocassign the result of the original pointer to the raw pointer to prevent unexpected memory problems.

Example:

#include <stdio.h>
#include <stdlib.h>
int main() {
    
    
    int *p = (int *) malloc(40);
    if (p == NULL)
        return 1;
    //使用
    int i = 0;
    for (i = 0; i < 10; i++) {
    
    
        *(p + i) = i;
    }

    for (i = 0; i < 10; i++) {
    
    
        printf("%d ", *(p + i));
    }
    //增加空间
    // p = (int *)realloc(p, 80); //如果开辟失败的话,p变成了空指针,不能这么写
    int *ptr = (int *) realloc(p, 80);
    if (ptr != NULL) {
    
    
        p = ptr;
        ptr = NULL;
    }
    //当realloc开辟失败的时候,返回的也是空指针
    //使用
    for (i = 10; i < 20; i++) {
    
    
        *(p + i) = i;
    }

    for (i = 10; i < 20; i++) {
    
    
        printf("%d ", *(p + i));
    }
    //释放
    free(p);
    p = NULL;
    return 0;
}

//输出结果:0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

2. Common dynamic memory errors

2.1 Dereference operation on NULL pointer

#include <stdio.h>
#include <stdlib.h>
int main() {
    
    
    int* p = (int*)malloc(20);
    *p = 5;  //错误,空指针解引用
    //为了不对空指针解引用  需要进行判断
    if (p == NULL) {
    
    
        perror("malloc");
        return 1;
    }
    else {
    
    
        *p = 5;
    }
    free(p);
    p = NULL;
    return 0;
}

2.2 Out-of-bounds access to dynamically allocated spaces

#include <stdio.h>
#include <stdlib.h>
int main() {
    
    
    int *p = (int *) malloc(20);
    if (p == NULL)
        return 1;
    int i = 0;
    for (i = 0; i < 20; i++)//越界访问    20个字节 只能访问5个整型
    {
    
    
        *(p + i) = i;
    }

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

2.3 Use free to release non-dynamic memory

#include <stdio.h>
#include <stdlib.h>
int main() {
    
    

    int a = 10;
    int* p = &a;
    free(p);// ok?

    return 0;
}

insert image description here

The compiler will directly report an error

2.4 Use free to release part of a dynamically allocated memory

#include <stdio.h>
#include <stdlib.h>
int main() {
    
    
    int *p = (int *) malloc(40);
    if (p = NULL)
        return 1;
    int i = 0;
    for (i = 0; i < 5; i++) {
    
    
        *p = i;
        p++;
    }
    //释放
    //在释放的时候,p指向的不再是动态内存空间的起始位置
    free(p);// p不再指向动态内存的起始位置
    p++;
    return 0;
}

2.5 Multiple releases of the same dynamic memory

#include <stdio.h>
#include <stdlib.h>
int main() {
    
    
    int* p = (int*)malloc(40);
    if (p == NULL)
        return 1;
    int i = 0;
    for (i = 0; i < 5; i++) {
    
    
        *(p + i) = i;
    }
    //重复free
    free(p);
    p = NULL;//如果将p赋值为NULL  就可以在free,否则编译器会直接报错
    free(p);

    return 0;
}

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

#include <stdio.h>
#include <stdlib.h>
int *get_memory() {
    
    
    int *p = (int *) malloc(40);

    return p;
}

int main() {
    
    
    int *ptr = get_memory();
    //使用

    //释放  如果不释放 就会导致内存泄漏
    free(ptr);
    return 0;
}

3. Memory allocation of C/C++ program

insert image description here

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

  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 by running functions, function parameters, return data, return address, etc.
  2. Heap area (heap): Generally allocated and released by the programmer, if the programmer does not release it, it may be recovered 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).

Ordinary local variables are allocated space in the stack area. The feature of the stack area is that the variables created above are destroyed when they go out of scope. However, 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.

4. Classic written test questions

4.1 Topic 1

#include <stdio.h>
#include <stdlib.h>
void GetMemory(char *p) {
    
    
    p = (char *) malloc(100);
}

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

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

What is the result of running the Test function?

Running Testthe function results in undefined behavior.

In GetMemorythe function, a local variable is passed in char *p, and when it is modified inside the function, it will not affect the pointer in the original calling function. This is because the parameters of the function are passed by value, that is, the function gets a copy of the actual parameter, and the modification of the parameter will not affect the original actual parameter.

In Testthe function, a NULL pointer is strpassed to GetMemorythe function, then GetMemorythe memory is allocated in the function and the new address is assigned to p. But this strhas no effect on it, strit is still a NULL pointer, pointing to unallocated memory.

Then, Testusing in the function strcpyto copy the string to strthe memory pointed to, but strthe memory pointed to has not been allocated, which will lead to undefined behavior.

In order to correctly allocate memory and use pointers, it is necessary to modify GetMemorythe function so that it returns the allocated memory address and Testreceive the returned pointer in the function. freeAlso, don't forget that you need to use functions to release dynamically allocated memory after you're done using it .

Rewrite 1:

#include <stdio.h>
#include <stdlib.h>
void GetMemory(char **p) {
    
    
    *p = (char *) malloc(100);
}

void Test(void) {
    
    
    char *str = NULL;
    GetMemory(&str);   //传指针的地址
    strcpy(str, "hello world");
    printf(str);
    //释放
    free(str);
    str = NULL;
}

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

Rewrite 2:

#include <stdio.h>
#include <stdlib.h>
char *GetMemory() {
    
    
    char *p = (char *) malloc(100);
    return p;
}

void Test(void) {
    
    
    char *str = NULL;
    str = GetMemory();   //接受返回的p
    strcpy(str, "hello world");
    printf(str);
    //释放
    free(str);
    str = NULL;
}

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

4.2 Topic 2

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

void Test(void) {
    
    
    char *str = NULL;
    str = GetMemory();
    printf(str);
}

What is the result of running the Test function?

In GetMemorythe function, a local array is defined char p[] = "hello world";, and the address of the array is returned to the caller. However, once GetMemorythe function finishes executing, its local variable ( parray) will be destroyed because it is a local variable of automatic storage class. Therefore, the returned pointer points to memory that is no longer valid.

In Testthe function, you GetMemoryassign the return value of to the pointer str, and then printfprint strwhat is pointed to using . Since GetMemoryan invalid pointer (pointing to a local array that has been destroyed) is returned, printfgarbage values ​​may be printed, or the program may crash, or cause other unpredictable results.

This problem is known as the "dangling pointer" problem, because pointers hang around pointing to memory locations that are no longer valid.

To solve this problem, you can consider using dynamic memory allocation to allocate memory for storing strings, and remember to use to release the memory after use free.

Modified code example:

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

char *GetMemory(void) {
    
    
    char *p = (char *)malloc(strlen("hello world") + 1);
    if (p != NULL) {
    
    
        strcpy(p, "hello world");
    }
    return p;
}

void Test(void) {
    
    
    char *str = NULL;
    str = GetMemory();
    if (str != NULL) {
    
    
        printf("%s\n", str);
        free(str); // 释放内存
    }
}

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

4.3 Topic 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 is the result of running the Test function?

The memory is not freed, resulting in a memory leak

Modified code example:

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

void Test(void) {
    
    
    char *str = NULL;
    GetMemory(&str, 100);
    if (str != NULL) {
    
    
        strcpy(str, "hello");
        printf("%s\n", str);
        free(str); // 释放内存
    }
}

4.4 Topic 4

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

What is the result of running the Test function?

str is released early, accessing str again will lead to wild pointer behavior

Guess you like

Origin blog.csdn.net/ikun66666/article/details/131986249