• 常见的动态内存的错误 和 柔性数组


    ✅<1>主页:C语言的前男友

    📃<2>知识讲解:常见的动态内存的错误,以及柔性数组(C99)

    🔥<3>创作者:C语言的前男友

    ☂️<4>开发环境:Visual Studio 2022

    💬<5>前言:之前我们说过动态内存管理的知识,今天我们来见识一下,动态内存管理常见的错误。帮助大家对动态内存管理有更深入的理解。

    目录

    🍍一.对NULL指针的解引用操作

    🌽二.对动态开辟空间的越界访问

    🏡三.对非动态开辟内存使用free释放

    🍐 四.使用free释放一块动态开辟内存的一部分

    🍉  五.对同一块动态内存多次释放

    🍓六.动态开辟内存忘记释放(内存泄漏) 

    🍉例题一.

    🍚 例二:

    🍒例三:

    🍊柔性数组:

    🍉  (1)定义:

    🍉  (2)柔性数组的特点:

    🍉  (3)柔性数组的使用

    🍭最后:

    不是一番寒彻骨,怎得梅花扑鼻香。


    🍍一.对NULL指针的解引用操作

    代码:

    1. void test()
    2. {
    3. int *p = (int *)malloc(INT_MAX/4);
    4. *p = 20;//如果p的值是NULL,就会有问题
    5. free(p);
    6. }

    如果 malloc 开辟空间失败就会形成对 NULL 指针的解引用

    🌽二.对动态开辟空间的越界访问

    代码:

    1. void test()
    2. {
    3. int i = 0;
    4. int* p = (int*)malloc(10 * sizeof(int));
    5. if (NULL == p)
    6. {
    7. exit(EXIT_FAILURE);
    8. }
    9. for (i = 0; i <= 10; i++)
    10. {
    11. *(p + i) = i;//当i是10的时候越界访问
    12. }
    13. free(p);
    14. }

    malloc开辟的空间与数组一样,只不过是在堆上开辟的空间,但是也是不能越界访问的。例如上述代码我们只开辟了,是个 int 的空间所以我们在访问时就只能访问是个整形。

    🏡三.对非动态开辟内存使用free释放

    1. void test()
    2. {
    3. int a = 10;
    4. int* p = &a;
    5. free(p);//ok?
    6. }

    🍐 四.使用free释放一块动态开辟内存的一部分

    1. void test()
    2. {
    3. int* p = (int*)malloc(100);
    4. p++;
    5. free(p);//p不再指向动态内存的起始位置
    6. }

    ​这个时候在对空间进行释放就会出错。

    🍉  五.对同一块动态内存多次释放

    代码:

    1. void test()
    2. {
    3. int* p = (int*)malloc(100);
    4. free(p);
    5. free(p);//重复释放
    6. }

    🍓六.动态开辟内存忘记释放(内存泄漏) 

    代码:

    1. void test()
    2. {
    3. int* p = (int*)malloc(100);
    4. if (NULL != p)
    5. {
    6. *p = 20;
    7. }
    8. }
    9. int main()
    10. {
    11. test();
    12. while (1);
    13. }

    忘记释放不再使用的动态开辟的空间会造成内存泄漏。
    切记:
    动态开辟的空间一定要释放,并且正确释放,虽然代码运行起来不会有什么错误,但是内存会被一点一点吃掉,直到内存吃完了,程序停止。

    🍉例题一.

    1. void GetMemory(char* p)
    2. {
    3. p = (char*)malloc(100);
    4. }
    5. int main()
    6. {
    7. char *str=NULL;
    8. GetMemory(str);
    9. strcpy(str, "hello world");
    10. printf(str);
    11. }

    这里的大概思路是,定义一个指针str ,调用GetMemory函数让指针指向 malloc 开辟的空间。再将字符串拷贝到 str 所指向的空间内,最后打印字符串。

    但是事实却是:

     为什么会出现这样的错误呢?

    看似我们让指针指向了 malloc 开辟的空间,但是事实上并没有改变 str 的值,因为形参 p,和实参 str,他俩只是值相同而已,对形参 p 的改变,不会影响实参 str 。所以 str 还是空的。所以后面在调用 strcpy 的时候就会出现对NULL指针的使用错误。程序就会崩掉。

    这是这个代码的最大的问题,还有一些小问题,这里的 malloc 的空间并没有释放。

    那这个代码改怎么改呢?我们是希望malloc开辟的空间能够交给 str 来指引。意思就是能过改变 str 的指向,使得 str 原本指向NULL,变成malloc开辟的空间。这里就想到了传址调用。所以我们要用str 的地址作为函数参数。形参用二级指针接受。

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

    还有一些同学应该早早就发现了,这里的打印字符串是不是有错误啊?我们平时不是这样的写的呀。

    printf(str);

    这里的打印字符串是没有错误的。

    🍚 例二:

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

     大致思路是,创建一个指针 str ,调用函数 GetMemory,函数内部创建一个字符串数组,并返回 数组名。用指针 str 经行接收,并打印。

    一切都是那么的合情合理,但是结果总是那么的不尽人意。

     简单分析一下,我们知道函数都是在栈上面开辟函数栈帧的,函数内部创建的变量都是局部变量,局部变量随着函数栈帧的销毁而销毁。当函数返回以后就意味着函数的栈帧也就销毁了。里面的局部变量也就不再是有效的局部变量了。就算返回了那块空间的地址,但是那块空间的内容已经无效了。所以才出现这种打印出的乱码。这是一种常见的错误叫做:返回栈空间地址。

    🍒例三:

    1. int main()
    2. {
    3. char* str = (char*)malloc(100);
    4. strcpy(str, "hello");
    5. free(str);
    6. if (str != NULL)
    7. {
    8. strcpy(str, "world");
    9. printf(str);
    10. }
    11. return 0;
    12. }

     这里大家看到代码成功跑起来了,也得到了想要的结果。这总该没问题了吧。

    其实这个代码还是有问题的,只不过编译器没有发现而已。

    问题在于我们申请的空间,交给 str ,但是已经释放了,free以后就相当于是把malloc申请的空间还给 OS 了,但是 str 还是指向那块空间的。所以 str 仍不为空。 但是这里再将 "world"拷贝给 str,就是有问题的了。

    🍊柔性数组:

    🍉  (1)定义:

    也许你从来没有听说过柔性数组(flexible array)这个概念,但是它确实是存在的。
     C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员。

    实现:

    1. typedef struct st_type
    2. {
    3. int i;
    4. int a[0];//柔性数组成员
    5. }type_a;

    有些编译器会报错无法编译可以改成:

    1. typedef struct st_type
    2. {
    3. int i;
    4. int a[];//柔性数组成员
    5. }type_a;

    🍉  (2)柔性数组的特点:

     1.结构中的柔性数组成员前面必须至少一个其他成员。

    2.sizeof 返回的这种结构大小不包括柔性数组的内存。

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

    🍉  (3)柔性数组的使用

    1. typedef struct st_type
    2. {
    3. int i;
    4. int a[0];//柔性数组成员
    5. }type_a;
    6. int main()
    7. {
    8. //创建结构体,sizeof(int)*5为柔性数组开辟五个整形空间。
    9. type_a* stra = (int*)malloc(sizeof(type_a) + sizeof(int) * 5);
    10. stra->i = 100;
    11. for (int i = 0; i < 5; i++)
    12. {
    13. stra->a[i] = i;
    14. }
    15. for (int i = 0; i < 5; i++)
    16. {
    17. printf("%d ", stra->a[i]);
    18. }
    19. printf("stra->i=%d", stra->i);
    20. return 0;
    21. }

       

    🍭最后:

    不是一番寒彻骨,怎得梅花扑鼻香。

  • 相关阅读:
    基于matlab的排队系统仿真
    好用软件推荐
    亚马逊云科技-游戏孵化营 -学习心得
    使用vba调用vb.net封装的dll,出现453错误
    Panorama SCADA平台的警报通知功能配置详解
    使用verdaccio+docker搭建npm私有仓库以及使用
    继承于QObject 的多线程实现
    普通人下场全球贸易,新一轮结构性机会浮出水面
    1052 卖个萌
    深度学习开发环境搭建
  • 原文地址:https://blog.csdn.net/qq_63943454/article/details/127060459