13.动态内存管理:malloc,calloc,realloc和free

在以前的C语言学习中,可以通过数组、二维数组的形式开辟空间。
而上述方法不可避免的会出现以下情况:
  • 1. 空间开辟大小是固定的。
  • 2. 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配。
  • 3. 开辟的空间在内存上,无法存储大量数据
但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道, 那数组的编译时开辟空间的方式就不能满足了,这时候就只能试试动态存开辟了。
(当然,数据量足够庞大时还是需要以数据库的形式存储,再存储到内存上就不合适了)

目录

malloc和free

realloc

calloc

常见的动态内存错误


系统在接收到分配一定大小内存的请求时,首先查找内部维护的内存空闲块表,并且需要根据一定的算法(例如分配最先找到的不小于申请大小的内存块给请求者,或者分配最适于申请大小的内存块,或者分配最大空闲的内存块等)找到合适大小的空闲内存块。

如果该空闲内存块过大,还需要切割成已分配的部分和较小的空闲块。然后系统更新内存空闲块表,完成一次内存分配。类似地,在释放内存时,系统把释放的内存块重新加入到空闲内存块表中。如果有可能的话,可以把相邻的空闲块合并成较大的空闲块。

malloc和free

malloc是C语言中用来动态开辟内存的,通过malloc函数可以向计算机申请一串连续的内存空间。

malloc和free使用的基本方式:

void* malloc (size_t size);

void free (void* ptr);

  • size是指定的开辟内存的大小,单位是字节
  • size_t的无符号整型则限制程序员误操作开辟负字节的空间
  • 如果开辟成功,malloc会返回一个void*类型的指针
  • 如果开辟失败,则返回的是空指针,所以在malloc之后需要对指针进行检查
  • 当malloc的东西不再使用时,需要free对其进行释放,否则会造成内存泄漏
  • malloc和free均需要包含头文件<stdlib.h>

示例:

#include<stdio.h>
#include<stdlib.h>
int main()
{
	int* pf = (int*)malloc(4);
	if (pf == NULL)
	{
		return -1;
	}
	*pf = 10;
	free(pf);
	pf = NULL;
	return 0;
}

malloc开辟了4个字节的空间,并且其返回值由void*类型的强制转换成int*类型,然后赋值给pf,pf通过解引用操作可以对其内存进行访问。

当pf不再使用时,用free(pf)释放掉pf指向的内存,并将pf置成空,防止后续继续访问造成野指针。

接下来演示关于malloc更加灵活的应用:

示例:存储学生学号 

malloc开辟结构体大小内存

typedef struct class
{
	int zhangsan;
	int lisi;
	int wangwu;
}class,*pclass;

int main()
{
	pclass class1  = (pclass)malloc(sizeof(class));
	if (class1 == NULL)
	{
		return -1;
	}
	class1->lisi = 210857;
	class1->wangwu = 210858;
	class1->zhangsan = 210859;
	printf("%d %d %d\n", class1->lisi, class1->wangwu, class1->zhangsan);
	return 0;
}
  • 上述代码先声明了结构体class,然后用typedef对结构体进行重定义。
  • sizeof(class)求的是一个结构体所占空间的大小。
  • 开辟空间后,将其转换成pclass类型的指针,也就是struct class*类型的。

当学习完单链表后,可以以更加灵活的方式存储学生的信息,单链表中也需要malloc去开辟新节点的信息。 

malloc实现一维、二维数组功能

在平时创建二维数组时,必须指定列数,而数组一旦开辟就不能更改其空间大小。而malloc开辟的空间还可以通过realloc重新生成内存。

示例:

实现一维数组功能:

int* arr = (int*)malloc(sizeof(int) * 10);//实现一维数组
	int i, j;
	if (arr == NULL)return -1;
	for (i = 0; i < 10; i++)
	{
		arr[i] = i;
	}
	free(arr);

其赋值方式也跟一维数组一致,通过调试对arr进行观察,可以发现内存是连续且都被赋了值的。 

 实现二维数组的功能

    int** arr2 = (int**)malloc(sizeof(int*) * 10);//实现二维数组
    int i, j;
    if (arr2 == NULL)return -1;
	for (i = 0; i < 10; i++)
	{
		arr2[i] = (int*)malloc(sizeof(int) * 10);
	}
	if (*arr2 == NULL)return -1;
	for (i = 0; i < 10; i++)
	{
		for (j = 0; j < 10; j++)
		{
			arr2[i][j] = i + j;
		}
	}
	free(arr2);

malloc先开辟了10个int*类型的空间大小,并将返回值转换成int**类型,赋值给arr2。

通过for循环,对每个int*空间开辟10个int类型的空间大小。

对arr2进行打印,malloc确实实现了二维数组的功能。

通过上述代码发现,malloc开辟好空间后,其使用方式和数组的方式是一样的,而且相较于数组更加灵活,易于内存的管理。

realloc

realloc函数的出现让动态内存管理更加灵活。有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时候内存,我们一定会对内存的大小做灵活的调整。那realloc 函数就可以做到对动态开辟内存大小的调整。

realloc函数原型如下:

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

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

realloc在调整内存空间的是存在两种情况:

1、原空间后续由足够大的空间。

2、原空间后续没有足够大的空间,需要重新申请一块内存,将原来内存中的数据拷贝到新内存中,并且将新内存的地址返回。

鉴于此,频繁的使用realloc重新开辟内存也会导致程序缓慢。

并且当重新申请的内存过大时,也会产生错误,例如:

int main()
{
	int* a = (int*)malloc(40);
	if (a == NULL)return -1;
	a = (int*)realloc(a,1000000000000000000000);
    free(a);
	return 0;
}

这时编译器就会报警告:常量太大

calloc

C语言还提供了一个函数叫calloc,calloc函数也用来动态内存分配。

原型如下:

void* calloc (size_t num, size_t size);

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

示例:

int main()
{
	int* a = (int*)calloc(10,sizeof(int));
	if (a == NULL)return -1;
	free(a);
	a = NULL;
	return 0;
}

 通过观察内存发现,所有变量已被初始化为0。

 

动态管理的不足 

如果应用程序频繁地在堆上分配和释放内存,则会导致性能的损失。并且会使系统中出现大量的内存碎片,降低内存的利用率,这时会引入一个新的概念——内存池。

内存池:在真正使用内存之前,先申请分配一定数量的、大小相等(一般情况下)的内存块留作备用。当有新的内存需求时,就从内存池中分出一部分内存块,若内存块不够再继续申请新的内存。这样做的一个显著优点是,使得内存分配效率得到提升。

这里先引入内存池的概念,在后续再对其进行详细讲解。

 

常见的动态内存错误

  • 对NULL指针的解引用操作 
  • 对动态开辟空间的越界访问
  • 对非动态开辟内存使用free释放
  • 使用free释放一块动态开辟内存的一部分
  • 对同一块动态内存多次释放
  • 动态开辟内存忘记释放(内存泄漏)

  • 8
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

是元笙阿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值