• 【C语言进阶(11)】动态内存管理


    引用头文件

    stdlib.h

    Ⅰ 存在动态内存分配的原因

    内存使用的方式

    1. 创建变量(开辟一块独立的空间)

      • 局部变量 -> 栈区。
      • 全局变量 -> 静态区。
    2. 创建数组(开辟一块连续的 空间)

      • 局部数组 -> 栈区。
      • 全局数组 -> 静态区。

    上述开辟空间的方式有两个特点

    1. 空间开辟的大小是固定的
    2. 数组在定义的时候,必须指定数组的长度,它所需要的内存在编译时分配。

    存在的问题

    • 不能灵活的开辟空间。如果需要的空间很少,但是固定开辟的空间非常大,就会造成内存浪费。如果需要的空间很多,固定开辟的空间很小,就会造成内存不够用的情况。
    • 因此就出现了动态内存函数,用来随时更改所开辟的空间的大小。

    Ⅱ 动态内存函数

    1. malloc

    函数原型

    void* malloc (size_t size);
    
    • 1

    函数功能

    • 开辟一块 size 个字节大小连续可用的空间,并返回该空间的起始地址。

    函数返回值

    • 如果开辟成功,则返回一个指向开辟好空间的起始地址的指针。
    • 如果开辟失败,则返回一个 NULL 指针。因此要检查好 malloc 的返回值。
    • 返回值的类型是 void*,在使用指向开辟好的空间的起始地址的指针时,还需要转换成自己需要的指针类型。

    函数用例

    • 申请一块空间,用来存放 10 个整形。
    #include 
    
    int main()
    {
    	int* p = (int*)malloc(10 * sizeof(int));
    	//malloc 返回的是 void* 的指针,需要转换成对应的指针类型才能使用
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2. calloc

    函数原型

    void* calloc (size_t num, size_t size);
    
    • 1

    函数功能

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

    函数用例

    • 开辟一块具有 10 个元素,每个元素 4 个字节的空间。

    在这里插入图片描述

    3. realloc

    函数原型

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

    函数功能

    • 调整由 ptr 所指向的空间大小为 size 个字节。

    函数参数

    • ptr:指向由 malloc、calloc 所开辟的空间。
    • size:新空间的大小(字节)。

    realloc 函数的两种使用情况

    void* realloc (void* ptr, size_t size);
    
    • 1
    1. 将 ptr 所指向的空间大小重新调整为 size 个字节。
    void* realloc(NULL,size_t size);
    
    • 1
    1. 开辟一块有 size 个字节的空间。

    函数用例

    在这里插入图片描述

    realloc 调整空间的方式

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

    1. 情况 1:原有空间之后足够大的空间,扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化,返回旧地址

    在这里插入图片描述

    1. 情况 2:原有空间之后没有足够大的空间,realloc 会重新找一个内存区域开辟一块符合要求的空间,返回新地址,拷贝旧数据,释放旧空间

    在这里插入图片描述

    4. free (重要)

    函数原型

    void free (void* ptr);
    
    • 1

    函数参数

    • ptr:指向先前用 malloc、calloc、realloc 分配的内存块的起始地址。

    函数功能

    • 释放由 ptr 指向的动态开辟的空间。

    函数用例

    • 释放由各个动态内存函数所开辟的空间。
    int main()
    {
    	int* p1 = (int*)malloc(10 * sizeof(int));
    	int* p2 = (int*)calloc(10, sizeof(int));
    	int* p3 = (int*)realloc(NULL, sizeof(int) * 10);
    	
    	//......
    	
    	free(p1);
    	free(p2);
    	free(p3);
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    注意事项

    • free 会将指针指向的空间释放掉,但是不会改变指针内存放的地址值。
    • 也就是说,即使 free 掉了 p1 p2 p3 三个指针所指向的空间,这三个指针依然记得原先空间的起始地址。
    • 因此释放掉指针所指向的空间之后,还必须要将该指针变量的值置为 NULL。
    int main()
    {
    	int* ptr = (int*)malloc(10 * sizeof(int));
    
    	//......
    
    	free(ptr);	//释放掉ptr指向的空间后,ptr内存的地址值未变,还是记得原来空间的地址
    	ptr = NULL;	//因此要主动修改ptr内存放的地址值为NULL
    	
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    Ⅲ 常见动态内存错误

    1. 对 NULL 指针的解引用操作

    • 在使用之前未判断动态内存函数的返回值是否是 NULL,如果内存开辟失败返回的是 NULL,就成了对 NULL 指针的解引用操作。
    int* p = (int*)malloc(40);
    *p = 20;//使用前为判断指针 p 内的值是否有效
    
    • 1
    • 2

    正确做法

    int* p = (int*)malloc(40);
    
    if (NULL == p)	//使用之前先判断该空间是否开辟成功
    {
    	return 1;	//1 为异常返回
    }
    
    *p = 20;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

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

    • 动态申请的空间也有着自己的范围,不能无限制的使用。
    int* p = calloc(10, sizeof(int));
    
    if (NULL == p)
    {
    	perror("calloc");
    	return 1;
    }
    	
    for (int i = 0; i <= 10; i++)//当 i 到 10 时越界访问
    {
    	printf("%d ", *(p + i));
    }
    free(p);
    p = NULL;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

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

    • free 只能释放由 malloc、calloc、realloc 开辟的空间。

    在这里插入图片描述

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

    • 指向开辟空间起始地址的指针产生变动,没有从动态开辟内存的起始地址开始释放空间。

    在这里插入图片描述

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

    • 忘记了已经释放过动态开辟的空间,又重新释放了一遍。
    int* p = (int*)malloc(10 * sizeof(int));
    //......
    free(p);//此时已经释放过该空间一次
    //.....
    free(p);//睡蒙了又释放一次
    
    • 1
    • 2
    • 3
    • 4
    • 5

    解决办法

    • 在释放完动态开辟好的内存后,及时将该指针置为 NULL。之后如果再重复释放也不会产生问题。

    6. 动态开辟内存没有释放(内存泄漏)

    • 只记得申请空间,不记得还。
    void test()
    {
    	int* p = (int*)malloc(10 * sizeof(int));
    	//p 是局部变量,出了作用域就销毁,等这个函数一结束就没人再记得这块空间的起始地址了
    
    	if (true)//此时动态开辟的空间永远没机会释放了
    	{
    		return;
    	}
    
    	free(p);
    	p = NULL;
    }
    
    int main()
    {
    	test();
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    Ⅳ 柔性数组

    • C99 中,结构中的最后一个元素允许是未知大小数组,这就叫做『柔性数组』成员。
    struct S
    {
    	char c;
    	int i;
    	int arr[];	//未知大小数组 - 柔性数组成员
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    柔性数组的特点

    • 结构体中的柔性数组成员前面必须至少有一个其他成员。
    • sizeof 返回的这种结构体大小不包含柔性数组成员的大小。
    • 包含柔性数组成员的结构体用 malloc 函数进行内存的动态分配,并且分配的内存应该大于结构体的大小,以适应柔性数组的预期大小。
    struct S
    {
    	char c;
    	int i;
    	int arr[];
    };
    
    int main()
    {
    	struct S* ps = (struct S*)malloc(sizeof(struct S) + 20);
    	//这 20 个字节才是分配给柔性数组 arr 的
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    Ⅴ 动态创建二维数组

    • 假设现在要创建一个 3 行 5 列的二维数组。

    第一步

    • 先创建 3 个一维数组,并且返回这三个一维数组的第一行的地址。
    int** p = (int**)malloc(sizeof(int*) * 3);
    
    • 1

    在这里插入图片描述

    第二步

    • 为每个一维数组开辟具有 5 个元素的空间。
    for(int i = 0; i < 3; i++)
    {
    	*(p + i) = (int*)malloc(sizeof(int) * 5);
    }
    
    • 1
    • 2
    • 3
    • 4

    在这里插入图片描述

  • 相关阅读:
    【Leetcode每日一刷】贪心算法01:455.分发饼干、376. 摆动序列、53. 最大子序和
    MySQL约束之check
    Java入门基础知识
    计算机毕设(附源码)JAVA-SSM家政服务管理系统
    13 双口 RAM IP 核
    “阿里爸爸”最新Java面试指南,基础+框架+数据库+系统设计+算法
    一篇搞懂C++(万字总结)
    用5G制造5G,中国电信打造“滨江模式”,助力电子信息制造产业升级
    GreenPlum扩容节点
    FebHost:CO域名在搜索引擎排名中是否高于.COM域名?
  • 原文地址:https://blog.csdn.net/shangguanxiu/article/details/133544435