• 动态内存管理


    目录

    一、动态内存函数

    1、malloc

    2、free

    3、calloc

    4、realloc

    二、常见动态内存错误

    1、对NULL的解引用操作

    2、对动态开辟空间的越界访问

    3、对非动态开辟内存使用free释放

    4、使用free释放一块动态开辟内存的一部分

    5、对同一块动态内存多次释放

    6、动态开辟内存忘记释放(内存泄漏)

    三、经典笔试题

     笔试题一:

     笔试题二:

    笔试题三:

    笔试题四:

    四、C/C++程序的内存开辟

    五、柔性数组

    1、柔性数组的特点:

     2、柔性数组的优势:


    为什么会有动态内存分配呢?

    1. int a = 10;//在栈空间上开辟4个字节
    2. char arr[10] = { 0 };//在栈空间上开辟10个字节的连续空间

     上述开辟空间的方式:

    a.空间大小是固定的

    b.数组声明时,需指定数组长度,它所需要的内存在编译时分配

    但有时候所需空间大小在运行时才知道,就要用到动态内存开辟了。

    一、动态内存函数

    1、malloc

    void* malloc (size_t size);

    这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。

    a.开辟成功,则返回一个指向开辟好空间的指针

    b.开辟失败,则返回一个NULL指针(要检查malloc的返回值)

    c.返回值的类型是void*,malloc函数并不知道开辟空间的类型,要使用者自己强制类型转换

    2、free

    void free (void* ptr);

    free用于释放指针所指向的动态内存

    举个栗子:

    1. #include
    2. #include
    3. #include
    4. int main()
    5. {
    6. int arr[5] = { 0 };
    7. //动态内存开辟
    8. int* p = (int*)malloc(sizeof(arr));
    9. //检查返回类型是否是NULL指针
    10. if (p == NULL)
    11. {
    12. printf("%s\n", strerror(errno));
    13. }
    14. //使用
    15. int i = 0;
    16. for (i = 0; i < 5; i++)
    17. {
    18. *(p + i) = i;
    19. }
    20. for (i = 0; i < 5; i++)
    21. {
    22. printf("%d ", *(p + i));
    23. }
    24. //free释放动态内存
    25. free(p);
    26. p = NULL;
    27. return 0;
    28. }

    3、calloc

    void* calloc (size_t num, size_t size);

    calloc函数的功能是为num个大小为size的元素开辟一块空间,并且在返回地址之前把申请空间的每个字节初始化为0(这是与malloc函数的区别)

    举个栗子:

    1. #include
    2. #include
    3. #include
    4. int main()
    5. {
    6. //动态内存开辟
    7. int* p = (int*)calloc(10, sizeof(int));
    8. //检查p是否为NULL指针
    9. if (p == NULL)
    10. {
    11. printf("%s\n", strerror(errno));
    12. return 1;
    13. }
    14. //打印
    15. int i = 0;
    16. for (i = 0; i < 10; i++)
    17. {
    18. printf("%d ", *(p + i));
    19. }
    20. //释放
    21. free(p);
    22. p = NULL;
    23. return 0;
    24. }

    4、realloc

    realloc函数可以做到对动态开辟内存大小的调整

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

    ptr是要调整的内存地址

    size是调整之后的新大小

    返回值为调整之后的内存起始位置

    realloc在调整内存空间的时候存在两种情况:

    情况1:原有空间之后有足够大的空间

    此时有扩展内存直接在原有内存之后追加空间,原来空间的数据不发生变化

    情况2:原有之后没有足够大的空间

    此时在堆空间上另找一个合适大小的连续空间来使用,这样函数返回的是一个新的内存地址

     举个栗子:

    1. #include
    2. #include
    3. #include
    4. int main()
    5. {
    6. //动态开辟内存
    7. int* p = (int*)malloc(40);
    8. //检查返回值是否是空指针
    9. if (p == NULL)
    10. {
    11. printf("%s\n", strerror(errno));
    12. return 1;
    13. }
    14. //使用
    15. int i = 0;
    16. for (i = 0; i < 10; i++)
    17. {
    18. *(p + i) = i + 1;
    19. }
    20. //扩容
    21. int* ptr = (int*)realloc(p, 80);
    22. //确认不是空指针
    23. if (ptr != NULL)
    24. {
    25. p = ptr;
    26. }
    27. //使用
    28. for (i = 0; i < 10; i++)
    29. {
    30. printf("%d ", *(p + i));
    31. }
    32. //释放
    33. free(p);
    34. p = NULL;
    35. return 0;
    36. }

    二、常见动态内存错误

    1、对NULL的解引用操作

    1. void test()
    2. {
    3. int* p = (int*)malloc(40);
    4. *p = 20;//如果p的值是NULL,就会出现问题
    5. free(p);
    6. p = NULL;
    7. }
    8. int main()
    9. {
    10. test();
    11. return 0;
    12. }

    错误纠正:

    1. #include
    2. #include
    3. void test()
    4. {
    5. int* p = (int*)malloc(40);
    6. if (p == NULL)
    7. {
    8. printf("%s\n", strerror(errno));
    9. return 1
    10. }
    11. *p = 20;
    12. free(p);
    13. p = NULL;
    14. }
    15. int main()
    16. {
    17. test();
    18. return 0;
    19. }

    2、对动态开辟空间的越界访问

    1. #include
    2. #include
    3. void test()
    4. {
    5. int* p = (int*)malloc(40);
    6. if (p == NULL)
    7. {
    8. printf("%s\n", strerror(errno));
    9. return 1;
    10. }
    11. //使用
    12. int i = 0;
    13. for (i = 0; i <= 10; i++)//i=10时,数组越界访问
    14. {
    15. *(p + i) = i;
    16. }
    17. free(p);
    18. p = NULL;
    19. }
    20. int main()
    21. {
    22. test();
    23. return 0;
    24. }

    错误纠正:

    1. #include
    2. #include
    3. void test()
    4. {
    5. int* p = (int*)malloc(40);
    6. if (p == NULL)
    7. {
    8. printf("%s\n", strerror(errno));
    9. return 1;
    10. }
    11. //使用
    12. int i = 0;
    13. for (i = 0; i < 10; i++)
    14. {
    15. *(p + i) = i;
    16. }
    17. free(p);
    18. p = NULL;
    19. }
    20. int main()
    21. {
    22. test();
    23. return 0;
    24. }

    3、对非动态开辟内存使用free释放

    1. void test()
    2. {
    3. int a = 10;
    4. int* p = &a;
    5. free(p);
    6. p = NULL;
    7. }
    8. int main()
    9. {
    10. test();
    11. return 0;
    12. }

    4、使用free释放一块动态开辟内存的一部分

    1. #include
    2. #include
    3. void test()
    4. {
    5. //动态内存开辟
    6. int* p = (int*)malloc(40);
    7. //检查p是否为NULL
    8. if (p == NULL)
    9. {
    10. printf("%s\n", strerror(errno));
    11. return 1;
    12. }
    13. //使用
    14. int i = 0;
    15. for (i = 0; i < 5; i++)
    16. {
    17. *p = i;
    18. p++;
    19. }
    20. //释放
    21. free(p);
    22. p = NULL;
    23. }
    24. int main()
    25. {
    26. test();
    27. return 0;
    28. }

     此时p不再指向动态内存的起始位置

    5、对同一块动态内存多次释放

    1. int main()
    2. {
    3. int* p = (int*)malloc(40);
    4. free(p);
    5. free(p);
    6. }

    错误纠正:

    1. int main()
    2. {
    3. int* p = (int*)malloc(40);
    4. free(p);
    5. p = NULL;
    6. free(p);
    7. }

    6、动态开辟内存忘记释放(内存泄漏)

    1. void test()
    2. {
    3. int* p = (int*)malloc(40);
    4. int flag = 0;
    5. scanf("%d", &flag);
    6. if (flag == 5)
    7. {
    8. return;
    9. }
    10. free(p);
    11. p = NULL;
    12. }
    13. int main()
    14. {
    15. test();
    16. return 0;
    17. }

    如果输入5的话就忘记释放动态内存空间,造成内存泄露。

    1. int* test()
    2. {
    3. int* p = (int*)malloc(40);
    4. if (p == NULL)
    5. {
    6. return p;
    7. }
    8. return p;
    9. }
    10. int main()
    11. {
    12. int* ret = test();
    13. return 0;
    14. }

    如上代码的情况也容易释放动态内存。

    所以,一定要记得正确释放动态开辟的空间。

    三、经典笔试题

     笔试题一:

    1. void GetMemory(char* p)
    2. {
    3. p = (char*)malloc(100);//没有使用free释放动态内存空间,造成了内存泄露
    4. }
    5. void Test(void)
    6. {
    7. char* str = NULL;
    8. GetMemory(str);
    9. strcpy(str, "hello world");//形参p的值并没有带给str,str是NULL,解引用时程序会崩溃
    10. printf(str);
    11. }
    12. int main()
    13. {
    14. Test();
    15. return 0;
    16. }

    改正方法一:

    1. void GetMemory(char** p)
    2. {
    3. *p = (char*)malloc(100);
    4. }
    5. void Test(void)
    6. {
    7. char* str = NULL;
    8. GetMemory(&str);
    9. strcpy(str, "hello world");
    10. printf(str);
    11. free(str);
    12. str = NULL;
    13. }
    14. int main()
    15. {
    16. Test();
    17. return 0;
    18. }

     改正方法二:

    1. char* GetMemory(char* p)
    2. {
    3. p = (char*)malloc(100);
    4. return p;
    5. }
    6. void Test(void)
    7. {
    8. char* str = NULL;
    9. str = GetMemory(str);
    10. strcpy(str, "hello world");
    11. printf(str);
    12. free(str);
    13. str = NULL;
    14. }
    15. int main()
    16. {
    17. Test();
    18. return 0;
    19. }

     笔试题二:

    1. char* GetMemory(void)
    2. {
    3. char p[] = "hello world";
    4. return p;
    5. }
    6. void Test(void)
    7. {
    8. char* str = NULL;
    9. str = GetMemory();
    10. printf(str);
    11. }
    12. int main()
    13. {
    14. Test();
    15. return 0;
    16. }

    以上属于返回栈空间的地址问题 。

    笔试题三:

    1. void GetMemory(char** p, int num)
    2. {
    3. *p = (char*)malloc(num);
    4. }
    5. void Test(void)
    6. {
    7. char* str = NULL;
    8. GetMemory(&str, 100);
    9. strcpy(str, "hello");
    10. printf(str);
    11. }
    12. int main()
    13. {
    14. Test();
    15. return 0;
    16. }

    这道题确实用了传址调用,但却没有用free释放内存空间。

    1. void GetMemory(char** p, int num)
    2. {
    3. *p = (char*)malloc(num);
    4. }
    5. void Test(void)
    6. {
    7. char* str = NULL;
    8. GetMemory(&str, 100);
    9. strcpy(str, "hello");
    10. printf(str);
    11. free(str);
    12. str = NULL;
    13. }
    14. int main()
    15. {
    16. Test();
    17. return 0;
    18. }

    笔试题四:

    1. void Test(void)
    2. {
    3. char* str = (char*)malloc(100);
    4. strcpy(str, "hello");
    5. free(str);
    6. if (str != NULL)
    7. {
    8. strcpy(str, "world");//str是野指针
    9. printf(str);
    10. }
    11. }
    12. int main()
    13. {
    14. Test();
    15. return 0;
    16. }

     str指向的空间释放,但没有及时使置str=NULL。

    四、C/C++程序的内存开辟

    其中:

    栈区:主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。
     堆区:一般由程序员分配释放。。

    数据段(静态区):存放全局变量、静态数据。

    代码段:存放函数体(类成员函数和全局函数)的二进制代码。

    五、柔性数组

    柔性数组成员:结构体中最后一个元素允许是未知大小的数组。

    例如:

    1. struct Struct
    2. {
    3. int i;
    4. int arr[0];//柔性数组成员,0表示未知大小
    5. };
    6. struct Struct
    7. {
    8. int i;
    9. int arr[];//柔性数组成员
    10. };

    1、柔性数组的特点:

    a.柔性数组成员前必须至少有一个其他成员

    b.sizeof返回的结构体大小不包括柔性数组的内存

    例如:

    1. struct Struct
    2. {
    3. int i;
    4. int arr[0];
    5. };
    6. int main()
    7. {
    8. printf("%d\n", sizeof(struct Struct));//结构体的大小是4
    9. return 0;
    10. }

    c.柔性数组成员的结构用malloc()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小

    例如:

    1. struct S
    2. {
    3. int i;
    4. int arr[];
    5. };
    6. int main()
    7. {
    8. //柔性数组的使用
    9. struct S* p = (struct S*)malloc(sizeof(struct S) + 40);
    10. if (p == NULL)//检查
    11. {
    12. return 1;
    13. }
    14. p->i;
    15. for (int k = 0; k < 10; k++)
    16. {
    17. p->arr[k] = k;
    18. }
    19. //扩容
    20. struct S* p1 = (struct S*)realloc(p, sizeof(struct S) + 80);
    21. if (p1 == NULL)
    22. {
    23. p = p1;
    24. return 1;
    25. }
    26. free(p);//释放
    27. p = NULL;
    28. return 0;
    29. }

     2、柔性数组的优势:

    先看案例:

    1. typedef struct S
    2. {
    3. int i;
    4. int* a;//将柔性数组改为指针的形式
    5. }S;
    6. int main()
    7. {
    8. S* p = (S*)malloc(sizeof(S));//开辟
    9. if (p == NULL)//检查
    10. {
    11. return 1;
    12. }
    13. p->i = 100;
    14. p->a = (int*)malloc(40);//开辟
    15. if (p->a == NULL)
    16. {
    17. return 1;
    18. }
    19. //使用
    20. for (int k = 0; k < 10; k++)
    21. {
    22. p->a[k] = k;
    23. }
    24. //扩容
    25. int* p1 = (int*)realloc(p->a, 80);
    26. if (p1 == NULL)
    27. {
    28. return 1;
    29. }
    30. //释放
    31. free(p->a);
    32. p->a = NULL;
    33. free(p);
    34. p = NULL;
    35. return 0;
    36. }

     其实使用柔性数组的形式更好,原因如下:

    a.如果我们把结构体的内存以及其成员要的内存一次性分配好,返回给用户一个结构体指针,用户做一次free就可以把所有的内存也给释放掉

    b.有益于减少内存碎片,提高访问速度

     

    本次分享到此结束,后续会有不断更新,不要忘记一键三连噢~ 

  • 相关阅读:
    Java实习生常规技术面试题每日十题Java基础(五)
    IOS自带的OCR识别功能
    浏览器自动化利器Selenium IDE使用指南
    你真的了解IP地址吗?
    Java基础-面向对象进阶-static,继承
    《计算机体系结构量化研究方法第六版》1.6 成本趋势
    【云原生丨Docker系列7】Docker的四种网络模式详解
    在VR全景中嵌入3D模型有哪些优势?
    账户和权限管理
    18张图,我发现了国庆节的赚钱效应...
  • 原文地址:https://blog.csdn.net/lang_965/article/details/125745186