• 动态内存管理


    目录

    malloc

    返回栈空间地址问题: 

     free

    calloc

    realloc

    内存开辟

    柔性数组

    优点


    malloc

    void* malloc(size_t size)

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

    • 如果开辟成功,则返回一个指向开辟好空间的指针。
    • 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
    • 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
    • 如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。

    malloc失败导致对空指针解引用:   

    1. int *p = (int*)malloc(INT_MAX);
    2. *p = 5;
    3. free(p);
    4. p = NULL;

    记得返回动态开辟的空间地址,别忘了最后释放空间避免内存泄露:

    1. int* get_memory()//也可使用二级指针
    2. int* p = (int*)malloc(40);
    3. //....
    4. return p;
    5. }
    6. int main()
    7. {
    8. int *ptr = get_memory();
    9. //使用
    10. //释放
    11. free(ptr);
    12. ptr = NULL;
    13. return 0;
    14. }

    返回栈空间地址问题: 

    1. char* Getmemory()
    2. {
    3. const char* a = "hello";//存放在常量区返回后地址还在
    4. return (char*)a;
    5. }
    6. char* Getmemory()
    7. {
    8. char a[] = "hello";//栈区空间返回后被销毁
    9. return a;
    10. }
    11. int main()
    12. {
    13. char* p = Getmemory();
    14. printf("%s", p);
    15. return 0;
    16. }
    1. int* test()
    2. {
    3. int a = 10;
    4. return &a;
    5. }
    6. int main()
    7. {
    8. int*p = test();
    9. //printf("hehe\n");
    10. printf("%d\n", *p);
    11. return 0;
    12. }

    这段代虽然访问了被销毁的空间,但依然能正确输出,这可能是由于编译器并未对数据进行清理的缘故,依然是一段错误代码,当你在打印前加上个printf函数,它开辟的栈帧会建立在销毁的test函数上,可能就导致数据被篡改了。

     free

    free函数用来释放动态开辟的内存。

    • 如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
    • 如果参数 ptr 是NULL指针,则函数什么事都不做。
    1. int* p = (int*)malloc(40);
    2. if (p == NULL)
    3. return 1;
    4. int i = 0;
    5. for (i = 0; i < 5; i++)
    6. {
    7. *p = i;
    8. p++;
    9. }
    10. //释放
    11. //在释放的时候,p指向的不再是动态内存空间的起始位置
    12. free(p);
    13. p = NULL;

     注意释放时需要记录起始位置的地址。

    calloc

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

    realloc

    void* realloc(void *ptr, size_t size)

    对已经动态开辟的空间进行扩容(缩容),如果第一个参数为NULL则作用类似于malloc。

    如果扩容失败则返回NULL,所以扩容前最好将原地址保存起来。

    内存开辟

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

    柔性数组

    在c99中,结构体的最后一个成员可以设置成柔性数组(否则不知道对齐数)。

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

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

    1. typedef struct st_type
    2. {
    3. int i;
    4. int a[0];//柔性数组成员
    5. }type_a;
    • 结构中的柔性数组成员前面必须至少一个其他成员。(结构体大小是编译时分配,而柔性数组是动态开辟的
    • sizeof 返回的这种结构大小不包括柔性数组的内存。
    • 包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。

     使用柔性数组:

    1. struct S* ps = (struct S*)malloc(sizeof(struct S) + sizeof(int) * 4);//连续开辟
    2. if (ps == NULL)
    3. return 1;
    4. ps->n = 10;
    5. ps->s = 5.5f;
    6. for (int i = 0; i < 4; i++)
    7. {
    8. scanf("%d", &ps->arr[i]);
    9. }
    10. struct S*ptr = (struct S*)realloc(ps, sizeof(struct S)+10*sizeof(int));
    11. if (ptr == NULL)
    12. {
    13. return 1;
    14. }
    15. else
    16. {
    17. ps = ptr;
    18. }
    19. free(ps);
    20. ps = NULL;
    21. return 0;
    22. }

    使用指针:

    1. struct S
    2. {
    3. int n;
    4. float s;
    5. int *arr;//指针——>动态开辟
    6. };
    7. int mian()
    8. {
    9. struct S*ps = (struct S*)malloc(sizeof(struct S));
    10. if (ps == NULL)
    11. return 1;
    12. ps->n = 100;
    13. ps->s = 5.5f;
    14. int* ptr = (int*)malloc(4 * sizeof(int));
    15. if (ptr == NULL)
    16. {
    17. return 1;
    18. }
    19. else
    20. {
    21. ps->arr = ptr;
    22. }
    23. //使用
    24. int i = 0;
    25. for (i = 0; i < 4; i++)
    26. {
    27. scanf("%d", &(ps->arr[i]));
    28. }
    29. realloc(ps->arr, 10*sizeof(int));
    30. free(ps->arr);
    31. ps->arr = NULL;
    32. free(ps);
    33. ps = NULL;
    34. }

    优点

    首先,在内存分配中,连续分配的内存有利于减少内存碎片,提高访问速度。

    其次,当你在函数中对结构体及其指针成员进行两次动态分配后,可能会只释放了结构体而忘了释放成员在堆上开辟的空间,造成内存泄露。 

  • 相关阅读:
    Pytorch利用图像分割识别手势验证码(Deeplabv3)
    【scikit-learn基础】--『监督学习』之 逻辑回归分类
    Python中match...case的用法
    Java框架(四)--Spring AOP面向切面编程(3)--Spring AOP实现原理
    59页数字乡镇综合解决方案
    Vue3 实现 PDF 文件在线预览功能
    [附源码]Python计算机毕业设计Django高校商铺管理系统论文
    测试的核心价值到底是什么?
    【计算机网络】MTU和MSS
    JavaScript模块导入与编程
  • 原文地址:https://blog.csdn.net/dwededewde/article/details/133829877