• 动态内存管理


    目录

    目录

    动态内存函数

    malloc与free

    malloc原型

    使用解析

    free原型

    示范

    注意事项 

    calloc

    calloc原型

    示范

    realloc

    原型

    示例

    realloc开辟空间的两种情况

    常见的动态内存错误

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

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

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

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

    几个经典的笔试题

    001

    002

    003

    004

    C/C++程序内存分配的几个区域

    柔性数组

    柔性数组的特点

    柔性数组使用

    结束语 



    动态内存函数

    为什么存在动态内存分配?

            其实就是因为常见的开辟空间的方法是死的。当它们在创建好的同时,它们也同时被固定了,不变了,当在未来想要添加新的项目进去的时候并且之前旧的空间是不够用的时候,问题就出现了

    例如下面两种开辟的方式 

    int val = 20;//在栈空间上开辟四个字节
    char arr[10] = {0};//在栈空间上开辟10个字节的连续空间
    1. 空间开辟大小是固定的
    2. 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配

    将来就会出现一系列的麻烦,为此我们需要动态内存的开辟了

    malloc与free

    malloc原型

    void* malloc (size_t size); 

    使用解析

    1.返回部分:返回 void* 的指针类型 -- 也就是说使用的时候需要强制转化类型返回值的类型  是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。 (开辟失败的时候返回NULL) -- 一般是内存大小不够了(笑)   

    2.参数部分:无符号的整型 -- 也就是你想要开辟多少空间,如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器(不过开辟0字节是什么情况?--笑)

    free原型

    void free (void* ptr);  

    这两个通常是成对出现的,一个开辟空间,一个释放空间 

    需要注意的是free是释放动态内存的空间,是不能释放栈区的内存

    #include

    int main() {

        int* p = NULL;
        free(p);                //释放一个空指针free是不会发生什么事情的

        return 0;
    }                                       

    示范

    1. //动态内存管理
    2. #include
    3. #include//strerror使用 -- 转化出错误代码 --关于错误的信息在编译器里面保存了这里是拿出来
    4. #include //errno使用 -- 这个的功能是存储最新的错误信息
    5. #include
    6. int main() {
    7. int arr[10] = { 0 };
    8. //动态开辟
    9. int* p = (int*)malloc(40); //强制类型转化为需要的 int类型
    10. if (p == NULL) //开辟失败的时候提前结束
    11. {
    12. printf("%s\n", strerror(errno)); //使用报错的一些函数
    13. return 1; //C习惯上,返回1 则是出现异常
    14. }
    15. //使用
    16. int i = 0;
    17. for ( i = 0; i < 10; i++)
    18. {
    19. *(p + i) = i;
    20. printf("%d ", *(p + i));
    21. }
    22. printf("\n");
    23. return 0; //返回0 表示正常返回
    24. }

     232c4025315d4878954baad9d7cdc293.png

    当开辟失败的时候

    542aa7787d504cde9499bf482c2c0c90.png

    注意:这个失败是我在vs2022下的x86(32位)环境下运行出来的,本人测试过x64(64位)环境下是可以开辟这么大的空间的(科技的进步啊)

    内存申请空间的位置也是不一样的

    751789bad5cf4a1bb8133f7492da04a5.png

    注意事项 

            当然我们还需要注意关于内存泄漏的问题,我在这里没有free是因为我的程序退出了,系统会自动回收,当然如果以后有要二十四小时一直运行的程序时就要十分小心这一点了。

            假如malloc用多了容易在内存之间产生内存碎片,这里的内存碎片没有办法利用的化,那么就会造成内存利用率下降。还要一点是,堆区的空间是操作系统管理的,当要申请空间的时候,都要打断操作系统的执行,让操作系统申请。这样以来就会造成效率的下降和内存碎片等情况

    f0c920e914f84ee8ad5e6847a8e41ef8.png

    补图

    6ba9b1a14e9b41e6a97150ed04ccb491.png

    当想要不断的开辟空间的时候,现在的机器聪明很多了,不会出现一直开辟空间出现电脑不行的问题

    1. //malloc死循环
    2. #include
    3. int main()
    4. {
    5. while (1)
    6. {
    7. malloc(1);
    8. }
    9. return 0;
    10. }

     06c0e8e018404bfbb273dfcb5a162c16.png

    注意:在二十四小时运行的程序中仍然需要注意这一点,不用的话要把内存释放,不然发生内存泄漏是很危险的行为

    calloc

    calloc原型

     void* calloc (size_t num, size_t size);

    示范

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

     4a11f4a95d1f4cc0a942963bd693eecf.png

    特点 

    于此得知,它在使用的时候会把开辟的空间的内容初始化为0

    realloc

    原型

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

    解析

    1.返回部分:返回一个 void* 的指针 -- 函数是不知道什么是类型的数据的

    2.参数部分:(void*ptr)-- 指向以前用 malloc、 calloc 或 realloc 分配的内存块的指针,或者,这可以是一个空指针,在这种情况下分配一个新块(就像调用 malloc 一样)。

                        (size_t size)        -- 内存块的新大小,以字节为单位

    示例

    1. #include
    2. #include
    3. #include
    4. #include
    5. int main() {
    6. int* p = (int*)malloc(40);
    7. if (p == NULL)
    8. {
    9. printf("%s\n", strerror(errno));
    10. return 1;
    11. }
    12. //赋值初始化
    13. int i = 0;
    14. for ( i = 0; i < 10; i++)
    15. {
    16. *(p + i) = i;
    17. printf("%d ", *(p + i));
    18. }
    19. printf("\n");
    20. //扩容
    21. int *ptr = (int*)realloc(p, 80); //这里的80是新的空间的大小,也就是开辟了40字节个新的空间
    22. if (ptr != NULL)
    23. {
    24. p = ptr; //如果想继续用p来维护原来的数据,这样可以实现,
    25. } //而不应该使用p 来直接接收,防止扩容失败,p被赋值NULL,而找不到原来的地址了
    26. //使用
    27. for (i = 10; i < 20; i++)
    28. {
    29. *(p + i) = i;
    30. printf("%d ", *(p + i));
    31. }
    32. printf("\n");
    33. return 0;
    34. }

    923f7ffc60c347ceb9664dbf85421236.png

    realloc开辟空间的两种情况

    65a9be689fe841749605d0f3fbc6c81a.png

    看看这两种情况 

    87bc3581f0d6423b86e022cba594a47e.png

    常见的动态内存错误

    NULL指针的解引用操作  

    1. //对NULL指针的解引用操作
    2. #include
    3. int main() {
    4. int* p = (int*)malloc(40);
    5. if (p == NULL) //假如没有这里的if语句 当开辟失败的时候就会出问题
    6. return 1; //为此在使用动态开辟的时候要先判断是否成功开辟
    7. *p = 20;
    8. free(p); //用完要记得释放空间
    9. p = NULL;
    10. return 0;
    11. }

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

    1. //对动态开辟空间的越界访问 
    2. #include
    3. int main()
    4. {
    5. int* p = (int*)malloc(40);
    6. if (p ==NULL)
    7. {
    8. return 1;
    9. }
    10. //越界了
    11. int i = 0;
    12. for ( i = 0; i <= 10; i++) //即使是动态开辟的空间也要防止越界访问
    13. {
    14. p[i] = i;
    15. }
    16. free(p);
    17. p = NULL;
    18. return 0;
    19. }

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

    1. //对非动态开辟内存使用free释放
    2. #include
    3. int main()
    4. {
    5. int a = 10;
    6. int* p = &a; //这里并不是动态开辟的空间是不能被释放的
    7. //…………
    8. free(p);
    9. p = NULL;
    10. return 0;
    11. }

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

    1. #include
    2. int main()
    3. {
    4. int* p = (int*)malloc(40);
    5. if (p == NULL)
    6. {
    7. return 1;
    8. }
    9. //使用
    10. int i = 0;
    11. for (i = 0; i < 5; i++) //这里指到了中间的元素,是不能释放一部分的,程序会崩溃
    12. {
    13. *p = i;
    14. p++; //指针移动了,不再指向了开始的位置,因此是不能被释放的
    15. } //因此要改的话,应该写成 // p[i] 或者 *(p+i)
    16. //释放
    17. free(p);
    18. p = NULL;
    19. return 0;
    20. }

    6d8ffe4df92b4a28a1c9989a8f0a2acc.png

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

    1. #include
    2. int main()
    3. {
    4.     int* p = (int*)malloc(40);
    5.     //……
    6.     free(p); //一旦free后就要马上 p = NULL ,防止p变为野指针
    7.     //……
    8.     free(p); //释放两次相同的空间,程序会崩溃掉
    9.     return 0;
    10. }

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

    1. #include
    2. #include
    3. void test()
    4. {
    5. int* p = (int*)malloc(100);
    6. //……
    7. int flag = 0;
    8. scanf("%d", &flag);
    9. if (flag == 5)
    10. return;
    11. free(p); //这里就有可能会释放不了空间,从而造成内存的泄漏,并且这里是更彻底的泄漏
    12. p = NULL; //一旦函数返回就再也找不到了
    13. }
    14. int main()
    15. {
    16. test();
    17. return 0;
    18. }

    1. #include
    2. //一个开辟空间的函数
    3. int* test()
    4. {
    5. int* p = (int*)malloc(40);
    6. if (p == NULL)
    7. {
    8. return p;
    9. }
    10. //……
    11. return p;
    12. }
    13. int main()
    14. {
    15. int* ret = test(); //接收一个指针
    16. //后面忘记释放了,从而导致内存的泄漏
    17. return 0;
    18. }

    几个经典的笔试题

    001

    1. //下面调用Test会发生什么?
    2. #include
    3. #include
    4. #include
    5. void GetMemory(char* p)
    6. {
    7. p = (char*)malloc(100);
    8. }
    9. void Test(void) {
    10. char* str = NULL;
    11. GetMemory(str);
    12. strcpy(str, "hello world");
    13. printf(str);
    14. }
    15. int main()
    16. {
    17. Test();
    18. return 0;
    19. }

    159968bdc6a048f68cd773a3f2da1cb8.png

    修改

    1. #include
    2. #include
    3. #include
    4. void GetMemory(char** p)
    5. {
    6. *p = (char*)malloc(100);
    7. }
    8. void Test(void) {
    9. char* str = NULL;
    10. GetMemory(&str);
    11. //str存放的就是动态开辟的100字节的地址
    12. strcpy(str, "hello world");
    13. printf(str);
    14. //要记得释放
    15. free(str);
    16. str = NULL;
    17. }
    18. int main()
    19. {
    20. Test();
    21. return 0;
    22. }

    002

    1. #include
    2. char* GetMemory(void) {
    3. char p[] = "hello world"; //局部数组,在出函数的时候就会变成野指针
    4. return p; //空间在返回的时候,内容就有可能会被改了(当然也有可能不会)
    5. }
    6. void Test(void) {
    7. char* str = NULL;
    8. str = GetMemory();
    9. printf(str);
    10. }
    11. int main()
    12. {
    13. Test();
    14. return 0;
    15. }

     比如用以下的情况来说明

     444329c2b1b142afbcb11835f83f9b14.png

     4b1eeb5550674cb3a168e38b18e4751c.png

     上面的问题都是属于返回栈空间地址的问题

    003

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

    既然开辟了空间就要记得释放空间 

    b0d35f89872f474ca437dec7e3830e04.png

    004

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

     94d552ef070943e09e4b170de71ce227.png

    C/C++程序内存分配的几个区域

    1. 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。
    2. 堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分配方式类似于链表。
    3. 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。
    4. 代码段:存放函数体(类成员函数和全局函数)的二进制代码

     1a8fbaebf5e2439a896107ae07dbd199.png

    柔性数组

    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;

    柔性数组的特点

    1.结构中的柔性数组成员前面必须至少一个其他成员。
    2.sizeof 返回的这种结构大小不包括柔性数组的内存。
    3.包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。

    第二点·补3c45c533488845269b70700c4a51ebdc.png

    柔性数组使用

    1. #include
    2. #include
    3. struct S
    4. {
    5. int n;
    6. int arr[];
    7. };
    8. int main()
    9. {
    10. struct S* ps = (struct S*)malloc(sizeof(struct S) + 40);
    11. if (ps == NULL)
    12. {
    13. return 1;
    14. }
    15. ps->n = 100;
    16. int i = 0;
    17. for ( i = 0; i < 10; i++)
    18. {
    19. ps->arr[i] = i;
    20. }
    21. for ( i = 0; i < 10; i++)
    22. {
    23. printf("%d ", ps->arr[i] = i);
    24. }
    25. printf("\n");
    26. free(ps);
    27. ps = NULL;
    28. return 0;
    29. }

     下面的图我没有释放,记得需要释放的防止空间泄漏

    e65436680e40448594c92c19b33a8316.png

    当我们想要扩大arr的空间时只需要用realloc就行了

        struct S* ptr = (struct S*)realloc(ps, sizeof(struct S) + 80);    //这样就可以扩大继续使用了
        if (ptr != NULL)        //继续使用ps维护代码
        {
            ps = ptr;

            ptr = NULL;     //把ptr置为空指针
        }                        //加上这一段就行了

    比起下面的写法上面的柔性数组更具有优势 

    1. #include
    2. #include
    3. struct S
    4. {
    5. int n;
    6. int *arr;
    7. };
    8. int main()
    9. {
    10. struct S* ps = (struct S*)malloc(sizeof(struct S));
    11. if (ps == NULL)
    12. {
    13. return 1;
    14. }
    15. ps->n = 100;
    16. ps->arr = (int*)malloc(40);
    17. if (ps->arr == NULL)
    18. {
    19. return 1;
    20. }
    21. int i = 0;
    22. for (i = 0; i < 10; i++)
    23. {
    24. ps->arr[i] = i;
    25. }
    26. for (i = 0; i < 10; i++)
    27. {
    28. printf("%d ", ps->arr[i] = i);
    29. }
    30. printf("\n");
    31. //扩容
    32. int* ptr = (int*)realloc(ps->arr, 80);
    33. if (ptr != NULL) //继续使用ps维护代码
    34. {
    35. ps = ptr;
    36. ptr = NULL; //把ptr置为空指针
    37. }
    38. //释放
    39. free(ps->arr); //因为开辟了两次,所以要释放两次空间,注意先后顺序
    40. free(ps);
    41. ps = NULL;
    42. return 0;
    43. }

    另一种写法//没有柔性数组好,这个方法开辟的次数多,容易忘记释放,造成内存泄漏,
    并且开辟多了还造成内存利用率降低

    结束语 

    天若有情天亦老,人间正道是沧桑。    --致落入红尘的你

  • 相关阅读:
    轻松玩转树莓派Pico之三、Windows+Ubuntu虚拟机模式下VSCode C语言开发环境搭建
    安卓手机丢了,危险了!意外的7万美元的谷歌Pixel绕过锁屏
    抖音最新图文带货玩法实操分享!
    C专家编程 第8章 为什么程序员无法分清万圣节和圣诞节 8.5 原型在什么地方会失败
    DJ11 8086系列处理器(第二节课)
    十大开源机器人 智能体
    实操创建属于自己的亚马逊云科技VPS服务:Amazon Lightsail
    运行springBoot项目
    导入jdk源码并进行使用
    OpenCV(二十八):连通域分割
  • 原文地址:https://blog.csdn.net/weixin_67595436/article/details/125834386