• 动态内存操作(2)


    接上一篇文章http://t.csdn.cn/1ONDq,这次我们继续讲解关于动态内存的相关知识。

    一、常见的动态内存错误

    1.对NULL指针进行解引用操作

    1. #include
    2. #include
    3. #include
    4. int main()
    5. {
    6. int* p = (int*)malloc(INT_MAX/4);
    7. *p = 20;//如果没有足够的空间导致p为NULL,就会有问题
    8. //所以必须对malloc的返回值进行判断
    9. free(p);
    10. p = NULL;
    11. return 0;
    12. }

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

    1. int main()
    2. {
    3. int* p = (int*)malloc(40);
    4. if (p == NULL)
    5. {
    6. perror("malloc");
    7. return 1;
    8. }
    9. int i = 0;
    10. for (i = 0; i < 11; i++)
    11. {
    12. p[i] = 0;
    13. //原本只申请了十个整型的空间,但却访问十一个整型
    14. //所以造成越界访问
    15. }
    16. free(p);
    17. p = NULL;
    18. return 0;
    19. }

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

    1. int main()
    2. {
    3. int a = 0;
    4. int* p = &a;
    5. free(p);//p不是动态开辟的空间,不能释放
    6. p = NULL;
    7. return 0;
    8. }

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

    1. int main()
    2. {
    3. int* p = (int*)malloc(40);
    4. if (p == NULL)
    5. {
    6. perror("malloc");
    7. return 1;
    8. }
    9. int i = 0;
    10. for (i = 0; i < 5; i++)
    11. {
    12. p[i] = 0;
    13. p++;
    14. }
    15. //p++导致p不再指向这块空间的起始地址
    16. //所以如果释放p,等于释放这块空间的一部分(后五个整型空间)
    17. //这样就会出问题
    18. free(p);
    19. p = NULL;
    20. return 0;
    21. }

    5.对同一快动态内存多次释放

    1. int main()
    2. {
    3. int* p = (int*)malloc(40);
    4. if (p == NULL)
    5. {
    6. perror("malloc");
    7. return 1;
    8. }
    9. free(p);
    10. //。。。。。
    11. free(p);
    12. //有时候头脑不清醒就可能释放多次,这样就会出问题
    13. return 0;
    14. }

    6.动态开辟内存后忘记释放内存(最常见)

    即我们动态申请内存后,最后忘记用free释放了,这样就会造成内存泄漏

    二、几个关于动态内存的经典例题

    例题1、代码运行结果是什么?

    源代码:

    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. }
    12. int main()
    13. {
    14. Test();
    15. return 0;
    16. }

    问:这段代码运行结果会是什么呢?

    例题1解答:

    一是没有释放动态内存;

    二是会产生这样的错误:

    原因是:因为在Test函数中,把str指针置空,然后作为参数传给GesMemory函数,该函数形参用指针p接收,这样指针p也为NULL,然后给指针p动态开辟空间,函数结束,到strcpy函数,但我们注意,我们是给指针p开辟空间,但指针str是没有变的,很多伙伴想不清楚,这不是传址调用吗,p指针变了,str指针也应该跟着变呀?

    实则不然,我们应该注意参数是指针也不一定是传址调用,这里是指针之间赋值,应该同时上升一段层次,这里要二级指针才算传址调用,所以指针str是不会变的,还是NULL,既然是NULL,所以就没有足够的空间能放下strcpy的第二个参数,所以报错。

    例题2、代码运行结果是什么?

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

    问:这段代码运行结果是什么?

    例题2解答:

    会产生这样的结果:

    “很多小伙伴可能觉得,在GetMemory函数里面返回字符串的起始地址p,所以在Test函数里面用指针str来接收并打印,所以运行结果应该为打印字符串。”

    但实则不然,我们一定要注意每个变量的生命周期,数组p的生命周期就只在函数GetMemory里面,所以当该函数return后,里面的变量所占的空间都会被自动销毁(释放),既然p的空间已经被释放了,还赋值给指针str,所以str就是个野指针,再打印str,就造成非法访问内存了。

    这类问题属于:返回栈空间地址的问题

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

    如下图:

    C/C++ 程序内存分配的几个区域:
    1. 栈区( stack ):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。
    2. 堆区( heap ):一般由程序员分配释放, 若程序员不释放,程序结束时可能由 OS 回收。分配方式类似于链表。
    3. 数据段(静态区)( static )存放全局变量、静态数据。程序结束后由系统释放。
    4. 代码段:存放函数体(类成员函数和全局函数)的二进制代码。
    实际上普通的局部变量是在 栈区 分配空间的,栈区的特点是在上面创建的变量出了作用域就销毁。但是被static 修饰的变量存放在 数据段(静态区) ,数据段的特点是在上面创建的变量,直到程序 结束才销毁 ,所以生命周期变长。

    四、柔性数组

    (一)、柔性数组的概念:

    也许你从来没有听说过 柔性数组( flexible array 这个概念,但是它确实是存在的。
    C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员。
    如下:
    1. //定义一:
    2. struct S
    3. {
    4. int a;
    5. int arr[0];
    6. };
    7. //定义二:
    8. struct B
    9. {
    10. int a;
    11. char b;
    12. int arr[];
    13. };

    (二)、柔性数组的特点:

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

    (三)、柔性数组的使用:

    如下例:
    1. struct S
    2. {
    3. int a;
    4. int arr[];
    5. };
    6. int main()
    7. {
    8. //动态开辟了4+40个字节,因为柔性数组是不会被sizeof计算的
    9. //前面四个字节是给成员a的,后面四十个字节给柔性数组
    10. //因为柔性数组的大小是未知的,我们只需给出预期大小
    11. struct S* str = (struct S*)malloc(sizeof(struct S) + 40);
    12. //检查
    13. if (str == NULL)
    14. {
    15. perror("malloc");
    16. return 1;
    17. }
    18. //使用
    19. str->a = 10;
    20. int i = 0;
    21. for (i = 0; i < 10; i++)
    22. {
    23. str->arr[i] = i + 1;
    24. //printf("%d ", str->arr[i]);
    25. }
    26. //用realloc扩容,因为柔性数组大小未知,是可以改变的
    27. //将之前柔性数组的10个字节的大小扩容到15个
    28. struct S* p = (struct S*)realloc(str, sizeof(struct S) + 60);
    29. //检查
    30. if (p == NULL)
    31. {
    32. perror("realloc");
    33. return 1;
    34. }
    35. //使用
    36. str = p;
    37. for (i = 10; i < 15; i++)
    38. {
    39. str->arr[i] = i + 1;
    40. }
    41. //打印
    42. for (i = 0; i < 15; i++)
    43. {
    44. printf("%d ", str->arr[i]);
    45. }
    46. //释放
    47. free(str);
    48. str = NULL;
    49. return 0;
    50. }

    用malloc函数进行开辟空间,用realloc函数进行扩容,这样数组的大小就是可变的、柔性

    的,这就是柔性数组的特点。

    (四)、柔性数组的优势:

    1.方便内存释放:
    如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给用户。用户调用free 可以释放结构体,但是用户并不知道这个结构体内的成员也需要 free, 所以你不能指望用户来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存一次性分配好了,并返回给用户一个结构体指针,用户做一次free 就可以把所有的内存也给释放掉。
    2.这样有利于访问速度:
    连续的内存有益于提高访问速度,也有益于减少内存碎片。(其实,我个人觉得也没多高了,反正你跑不了要用做偏移量的加法来寻址)。
  • 相关阅读:
    已解决python -m pip install --upgrade pip命令升级报错
    Reactive的使用(reactive 和 shallowReactive使用上区别)
    不想花大价钱?这10款替代Axure的平替软件更划算!
    程序开机自启动(基于linux)
    tslib库的移植
    技术成长之路:人工智能
    解决npm ERR! Cannot read properties of null (reading ‘pickAlgorithm‘)
    驱动开发,基于gpio子系统编写LED灯的驱动,亮灭控制
    spring cloud alibaba 简介
    YOLO系列概述(yolov1至yolov7)
  • 原文地址:https://blog.csdn.net/hffh123/article/details/133349260