【C语言进阶】动态内存管理

在这里插入图片描述

个人主页:@Weraphael
✍作者简介:目前是C语言学习者
✈️专栏:C语言航路
希望大家多多支持,咱一起进步!
如果文章对你有帮助的话
欢迎 评论 点赞 收藏 加关注


目录

  • 一、动态内存分配的由来
  • 二、动态内存函数的介绍
      • 2.1 malloc函数
      • 2.2 free函数
      • 2.3 calloc函数
      • 2.4 realloc函数
  • 三、常见的动态内存错误
      • 3.1 对NULL指针的解引用操作
      • 3.2 对动态开辟空间的越界访问
      • 3.3 对非动态开辟内存使用free释放
      • 3.4 使用free释放一块动态开辟内存的一部分
      • 3.5 对同一块动态内存多次释放
      • 3.6 动态开辟内存忘记释放

一、动态内存分配的由来

我们已经张伟的内存开辟方式有:

int a = 3;//在内存空间开辟4个字节
char a[10] = {0};//在内存开辟10个字节且连续的空间

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

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

但是对于空间的需求,不仅仅是上述的情况。因为有时候我们需要的空间大小在程序运行的时候才知道,那么以上的方式就不能满足了。而动态内存分配恰好能在需要时随时开辟,不需要的时候随时释放,且分配的大小就是程序要求的大小

二、动态内存函数的介绍

2.1 malloc函数

【C语言进阶】动态内存管理_第1张图片

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

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

malloc函数实例】

strerror讲解传送门 -> 点击此处

#include 
#include  //malloc函数所需头文件
#include   //errno所需头文件
#include //strerror所需头文件

int main()
{
	//假设向内存申请40个字节,用来存放整型
	int* p = (int*)malloc(40);
	//判断返回值是否为空指针
	if (p == NULL)
	{
		//说明开辟内存空间失败了,那就显示为什么失败了
		printf("%s\n", strerror(errno));
		return 1;
	}
	//否则返回值就不为空指针
	//假设存放数据1~10
	for (int i = 0; i < 10; i++)
	{
		*(p + i) = 1 + i;
	}
	//打印
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", *(p + i));
	}
	return 0;
}

【C语言进阶】动态内存管理_第2张图片

2.2 free函数

malloc函数实例中,代码会存在隐患。malloc函数既然在内存中申请了空间,用完了就应该还给操作系统。所以C语言提供了另外一个函数free专门用做动态内存的释放和回收的

【C语言进阶】动态内存管理_第3张图片

解读:

  • free函数是用来释放内存块
  • free函数无返回值
  • 参数ptr是指向先前使用 malloc、calloc 或 realloc 分配的内存块的指针。
  • 如果参数ptr指向的空间不是动态开辟的,那么free函数的行为就是未定义的
  • 如果参数ptrNULL指针,则函数不会做任何事。

free函数实例(完善malloc函数实例)】

#include 
#include  //malloc函数所需头文件
#include   //errno所需头文件
#include //strerror所需头文件

int main()
{
	//假设向内存申请40个字节,用来存放整型
	int* p = (int*)malloc(40);
	//判断返回值是否为空指针
	if (p == NULL)
	{
		//说明开辟内存空间失败了,那就显示为什么失败了
		printf("%s\n", strerror(errno));
		return 1;
	}
	//否则返回值就不为空指针
	//假设存放数据1~10
	for (int i = 0; i < 10; i++)
	{
		*(p + i) = 1 + i;
	}
	//打印
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", *(p + i));
	}

	//释放申请的内存
	free(p);
	p = NULL;
	return 0;
}

释放完空间后,p = NULL的原因:
我们可以通过F10调试来观察
释放前:我们将数据1~10存放到内存了
【C语言进阶】动态内存管理_第4张图片

释放后:我们发现,p在释放后还指向释放前的空间

【C语言进阶】动态内存管理_第5张图片
所以,为了防止日后非法访问,我们就将p=NULL

2.3 calloc函数

C语言还提供一个函数calloccalloc函数也用来动态内存分配

【C语言进阶】动态内存管理_第6张图片

解读:

  • 参数num是开辟元素的个数
  • 参数size是每个元素的大小
  • callocmalloc区别在于calloc会在返回地址之前把申请的空间的每个字节初始化为0

【calloc函数实例】

calloc会将申请的空间全部初始化为0

#include 
#include 
#include 
#include 

int main()
{
	//向内存申请10个元素,每个元素都是int类型
	int* p = calloc(10, sizeof(int));
	//判断p是否为空指针
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	//否则不是空指针
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", *(p + i));
	}
	//用完释放空间free
	free(p);
	p = NULL;
	return 0;
}

【C语言进阶】动态内存管理_第7张图片

2.4 realloc函数

  • realloc函数的出现让动态内存管理更加灵活
  • 有时候会发现过去申请的空间太小了,有时候又会觉得申请的空间过大,那为了合理的使用内存,就要对内存的大小做灵活的调整。使用realloc函数就的功能就是调整动态开辟内存大小

【C语言进阶】动态内存管理_第8张图片

解读:

  • 参数ptr是要调整的内存地址
  • 参数size为调整之后的新大小
  • 返回值为调整之后的内存的起始地址
  • 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动带新的空间

realloc函数在调整内存空间的时候存在两种情况:

  1. 当原有空间之后有足够大的空间,要扩展内存就直接在原有内存之后直接追加空间,原来空间的数据不发生变化,返回的是原来的地址
  2. 当原有空间之后没有足够大的空间,扩展的方法:在堆空间上另找一个合适大小的连续空间来使用。这样函数返回的是一个新内存的地址

【realloc函数实例】

#include 
#include 
#include 
#include 

int main()
{
	//假设向内存申请20个字节来存放5个整型
	int* p = (int*)malloc(20);
	//判断p是否为空指针
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	//否则p就不是空指针
	//接下来就将5个整型全部赋值成1
	for (int i = 0; i < 5; i++)
	{
		*(p + i) = 1;
	}
	//那么接下来假设我还要再开辟5个空间(realloc)
	int* ptr = (int*)realloc(p, 40);
	if (ptr != NULL)//说明扩容成功
	{
		p = ptr;
	}
	//打印10个整型
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", *(p + i));
	}
	//释放空间(free)
	free(p);
	p = NULL;
	return 0;
}

【C语言进阶】动态内存管理_第9张图片

三、常见的动态内存错误

3.1 对NULL指针的解引用操作

#include 
#include 

int main()
{
	int* p = (int*)malloc(40);
	*p = 20;
	free(p);
	p = NULL;
	return 0;
}
  • p = NULL,对于空指针解引用可能会导致程序异常终止或拒绝服务。

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

#include 
#include 

int main()
{
	//向内存申请10个整型的空间
	int* p = (int*)malloc(10 * sizeof(int));
	if (p == NULL)
	{
		return 1;
	}
	for (int i = 0; i <= 10; i++)
	{
		*(p + i) = i;
	}
	free(p);
	p = NULL;
	return 0;
}
  • 当指针指向的范围超过数组的范围时,p就是野指针,而野指针是没有访问权限的地址,会造成非法访问

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

#include 
#include 

int main()
{
	int a = 10;
	int* p = &a;
	free(p);
	p = NULL;
	return 0;
}
  • 这个就是free函数的概念,free的参数指向的空间一定是动态开辟的,比如:mallocrealloccalloc。而这里的p指向的是一个变量的地址

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

#include 
#include 

int main()
{
	int* p = (int*)malloc(40);
	p++;
	free(p);
	p = NULL;
	return 0;
}
  • 要知道不管是前置++还是后置++,它都是会有副作用的。p++后,p就会指向p++的位置,然后使用free释放只会释放p后面的内存空间,只释放了一部分
    【C语言进阶】动态内存管理_第10张图片

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

#include 
#include 

int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		return 1;
	}
	free(p);
	free(p);
	return 0;
}
  • 对一次free回收了p申请的内存空间,此时的p就是野指针,而野指针是没有访问地址的权限,若再一次使用free来回收内存空间,可能会造成非法访问

3.6 动态开辟内存忘记释放

#include 
#include 

int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		return 1;
	}
	*p = 20;
	return 0;
}
  • 使用完内存空间后,并没有释放(free)内存空间,会导致内存泄漏

你可能感兴趣的:(C语言航路,c语言,学习,visual,studio)