C语言进阶版第18课—动态内存管理

1. 动态内存管理

  • 之前我们见过两种开辟内存空间的方式
int n = 10; //在栈空间上开辟4个字节的空间
int arr[10] = { 0 }; //在栈空间上开辟40个字节连续的空间

  那么我们为什么要开辟动态的内存呢?因为我们可以通过动态管理内存,从而可以更好的利用有限的内存空间,很大程度上避免了内存的浪费。但是,正是由于内存通过动态管理,也会常常出现越界访问,空间占用未释放等问题,因此在使用时需特别注意

  • 与动态内存相关的函数有4个,分别是mallocfreecalloc以及realloc

2. malloc和free函数

2.1 malloc函数

  • malloc函数的原形是:
  • void* malloc(size_t size);
  • 其中malloc向内存堆区申请size个字节连续的空间,申请成功则返回指向该空间的地址,返回类型是void*,如果申请失败则返回NULL
  • 如果size为0,那么malloc的行为是未定义的,取决于编译器

在这里插入图片描述


在这里插入图片描述


在这里插入图片描述


2.2 free函数

  • free函数的原形是:
  • void free(void* ptr)
  • free函数是用来释放动态开辟的内存,且只能释放动态开辟的内存
  • 如果ptr是空指针,那么free函数什么都不做
  • 如果ptr指向的空间不是动态开辟的,那么结果未定义

在这里插入图片描述


  为什么free释放完动态内存后还要将p赋值NULL
在这里插入图片描述


3. calloc和realloc函数

3.1 calloc函数

  • calloc函数的原形:
  • void* calloc(size_t num,size_t size)
  • calloc函数是为num个size大小的元素开辟空间,并把开辟的空间全部初始化为0
  • calloc函数同样也是返回指向开辟空间的地址

在这里插入图片描述


3.2 realloc函数

  • realloc函数的原形:
  • void* realloc(void* ptr, size_t size)
  • realloc函数是用来调整动态内存大小的,若我们创建的动态内存不合适,可通过realloc函数进行修改
  • ptr是要调整的内存地址,size是要调整的大小
  • realloc函数在调整原有的内存空间大小的基础上,还会将原来内存中的数据迁移到新的内存中
  • realloc函数修改动态内存有两种方式
  • 假设旧内存大小为20字节,重新申请到40字节

在这里插入图片描述


在这里插入图片描述


在这里插入图片描述


  • realloc(NULL,20)
  • 如果realloc函数的第一个参数是空指针,那么realloc函数就等价于malloc,就是在堆区申请20个字节的空间

4. 常见的动态内存错误

4.1 对NULL指针的解引用操作

  • 错误引用

在这里插入图片描述


  • 正确引用

在这里插入图片描述


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

在这里插入图片描述


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

在这里插入图片描述


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

在这里插入图片描述


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

在这里插入图片描述


4.6 动态开辟的内存忘记释放

在这里插入图片描述


在这里插入图片描述


5. 练习

5.1 练习1

在这里插入图片描述


  • 正解1

在这里插入图片描述


  • 正解2

在这里插入图片描述


  • 这里需要注意的是,malloc创建动态内存时要判断其返回值是否是NULL,以上两种正解都未判断

5.2 练习2

在这里插入图片描述


5.3 练习3

在这里插入图片描述


5.4 练习4

在这里插入图片描述


6. 柔性数组

  • c99中,结构体中的最后一个元素允许时未知大小的数组,称为柔性数组
  • 柔性数组只存在结构体中,且sizeof计算结构体大小时,不会将柔性数组计算在内

在这里插入图片描述


6.1 柔性数组的特点

  • 结构体中的柔性数组前面必须至少包含一个成员
  • 包含柔性数组成员的结构体用malloc函数为柔性数组进行动态内存分配,并且分配的内存应该大于结构体的大小

在这里插入图片描述


6.2 柔性数组的使用

  • 柔性数组的使用
//柔性数组的使用
#include <stdio.h>
#include <stdlib.h>
struct S
{
	int n;
	int arr[];
};
int main()
{
	struct S* p = (struct S*)malloc(4 + sizeof(int) * 5);
	//判断malloc返回值
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	p->n = 100;
	for (int i = 0; i < 5; i++)
	{
		p->arr[i] = i + 1;
	}
	//增加动态内存
	struct S* tmp = (struct S*)realloc(p, sizeof(struct S) + 40);
	//检查realloc的返回值
	if (tmp = NULL)
	{
		perror("realloc");
		return 1;
	}
	p = tmp;
	for (int i = 5; i < 10; i++)
	{
		p->arr[i] = i + 1;
	}
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", p->arr[i]);
	}
	//释放动态内存
	free(p);
	p = NULL;
	return 0;
}
  • 非柔性数组使用
//非柔性数组
#include <stdio.h>
#include <stdlib.h>
struct S
{
	int n;
	int* arr;
};
int main()
{
	struct S* p = (struct S*)malloc(sizeof(struct S));
	//判断malloc返回值
	if (p == NULL)
	{
		perror("malloc1");
		return 1;
	}
	p->n = 100;
	p->arr = (int*)malloc(5 * sizeof(int));
	//判断malloc返回值
	if (p->arr == NULL)
	{
		perror("malloc2");
		return 1;
	}
	for (int i = 0; i < 5; i++)
	{
		p->arr[i] = i + 1;
	}
	//增加动态内存
	int* tmp = (int*)realloc(p->arr, 10 * sizeof(int));
	//检查realloc的返回值
	if (tmp == NULL)
	{
		perror("realloc");
		return 1;
	}
	p->arr = tmp;
	for (int i = 5; i < 10; i++)
	{
		p->arr[i] = i + 1;
	}
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", p->arr[i]);
	}
	//释放动态内存
	free(p->arr);
	p->arr = NULL;

	free(p);
	p = NULL;
	return 0;
}
  • 从上面可以看出,柔性数组的访问方式更加便捷,有利于内存释放,而非柔性使用方法过于繁琐,为结构体开辟动态内存后,还要为arr重新再开辟动态内存,释放内存也是先释放arr,再释放结构体指针p,流程过于繁琐,且申请的两块动态内存不一定是连续的,对访问速度也有影响
  • 柔性数组是一次性开辟动态内存,方便释放内存和访问

7. c/c++中程序内存区域划分

在这里插入图片描述


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值