malloc、calloc、realloc和free

C 语言中开辟动态内存的有三个函数,分别为 malloc,calloc,realloc,释放内存的只有一个函数 free。
realloc的使用是最容易犯错的,在写这篇博客前老师让我把realloc的使用和注意事项仔细搜资料看看,在网上看了几篇博客之后还是决定写一篇博客,把这些东西都记录下来。
测试程序是否有内存泄漏推荐使用开源工具 vld,vld的安装和使用请点击此处跳转到vld教程博客

1.malloc的使用

malloc 使用的最频繁,因为它最简单,只需要一个参数,即需要动态开辟的内存的字节数,如果堆里的连续空间能满足需要则将分配好的内存首地址返回,否则返回 NULL。
malloc()函数的实质,它可以将可用的内存块连接成一个长长的链表,被称为空闲链表,当用户调用malloc函数时,它沿着链表找到比用户申请的内存的可用的内存块,如果内存块大了,它会把可用的内存一分为二,其中一块就是和用户申请的内存大小相等的内存块,它将这块内存块分配给用户(即将内存块首地址传给用户),另一块会返回到链表上。
调用free函数的时候,它会将用户释放的内存重新连接在链表上,注意,此时并没有和分隔开的第二块内存合并。最后空闲链表连接的内存可能是一小块一小块的,此时如果用户申请一个较大的内存块,那么这时候malloc函数就会找当前空闲内存,如果当前空闲内存无法满足用户申请的空间的大小,那么malloc就会将相邻的小内存块合并,如果最后将空闲的相邻的小内存块合并也无法满足用户申请的空间大小的要求,那么malloc函数就会返回NULL。
当我们用malloc申请空间时比如我们申请4字节大小空间,其实malloc内部,申请的空间要大于这个字节数。malloc需要在用户申请的空间的上部和下部多开辟4字节空间,作为上越界标记,和下越界标记,另外头部还有28字节空间用来记录空间使用情况等等,这些都是使用malloc的额外负担。

2.calloc的使用

函数原型:
void* calloc(int n, int size);
n是元素的数目
size是每个元素的大小
功能:在内存动态存储区中分配n块长度为size字节的连续区域calloc 只是在 malloc 的基础上将分配好的每个字节赋值为 0,这个功能使用并不多见,但由于 callloc 需要提供两个参数,相比较而言并没有 malloc 使用的多。

3.free的使用

free,用于释放内存,主要注意 free 可能引发程序崩溃的几个原因:
1、越界;
2,移动指针的指向,free 时指针不指向动态内存的开头;
3、重复释放同一段内存;
4、释放不是动态创建的内存。

4.realloc的使用

realloc原型
void* realloc(void* ptr,unsigned int newsize);
功能:改变ptr所指向的内存区域的大小为newsize的长度,并且将原内存中的数据拷贝到新内存中。

4.1realloc扩大内存的方式

我们都知道realloc是从堆上申请空间,当扩大一块内存空间时,realloc会先试图直接从现申请的堆空间后面进行扩充,如果现申请的堆空间后面有空间足够用于扩充,那么皆大欢喜,realloc直接在堆后面进行扩充。
但是如果堆后面的空间不够realloc扩充用呢?那么realloc就会在堆中重新找一块newsize大小的内存,然后将原内存的数据拷贝到新内存中,指针指向新内存。这说明一个问题,就是realloc扩充空间过后,原来指向内存的指针的指向很可能会有变化。
如果ptr为NULL,则realloc和malloc一样,分配一个newsize的空间,返回指向该空间首地址的指针。如果newsize大小为0,那么释放ptr所指向的内存,返回NULL。如果没有足够的可用的内存用来扩充,那么返回NULL,ptr指向的内存和数据都不改变。
我们通过对指针是否指向同一个地址的判断,来展现我们所说的现象。
下图这种情况属于直接在对后面空间扩充,导致p和q都指向堆空间。注意在这里我们没有对空间的释放,程序结束,空间自动释放,这里可能会出现悬挂指针的问题(即空间被释放,但是指针仍然指向这个空间),关于指针和空间释放的问题,我们在最下面再给出解决。这里只是说明realloc扩容的方式和对应的现象。

如下程序,我们只扩充了4字节,realloc发现已申请的堆空间后面有足够的空间,于是在堆后面紧接着扩充了4个字节。扩充后的空间的首地址和扩充前的空间的首地址一样,所以p和q指向了同一个空间。

#include<stdio.h>
#include<stdlib.h>

int main()
{
	int* p = (int*)malloc(sizeof(int) * 10);
	int* q = (int*)realloc(p, sizeof(int) * 11);
	if (q == NULL)
	{
		printf("realloc false\n");
	}
	if (q == p)
	{
		printf("p and q point the same space\n");
	}
	else
	{
		printf("realloc find a new space\n");
	}
	return 0;
}

在这里插入图片描述
下面这种情况就属于重新分配的空间,我们扩充的空间较大,realloc发现堆后面的空间不够扩容,就重新申请了一个newsize大小的空间,q指向新空间首地址,p指向的空间被释放,成为悬挂指针。

#include<stdio.h>
#include<stdlib.h>

int main()
{
	int* p = (int*)malloc(sizeof(int) * 10);
	int* q = (int*)realloc(p, sizeof(int) * 1024*1024*400);
	if (q == NULL)
	{
		printf("realloc false\n");
	}
	if (q == p)
	{
		printf("p and q point the same space\n");
	}
	else//p != q
	{
		printf("realloc find a new space\n");
	}
	/*free(q);
	free(p);*/
	return 0;
}

在这里插入图片描述

下面这种情况属于扩充失败,但是p指向的空间和数据都还存在,都不变都正常使用,realloc返回NULL,q == NULL。

#include<stdio.h>
#include<stdlib.h>

int main()
{
	int* p = (int*)malloc(sizeof(int) * 10);
	int* q = (int*)realloc(p, sizeof(int) * 1024*1024*500);
	if (q == NULL)
	{
		printf("realloc false\n");
	}
	else if (q == p)
	{
		printf("p and q point the same space\n");
	}
	else
	{
		printf("realloc find a new space\n");
	}
	return 0;
}

在这里插入图片描述

4.2指针在 realloc时正确的使用方式

在讨论realloc中我们已经知道了realloc的扩容方式,以及指针存在的问题,对上面代码中的指针而言:
(1)如果扩容成功,p指针指向的堆空间将被释放,p指针不处理的话,将会成为悬挂指针。
(2)如果扩容失败,q指针将为NULL,p指针指向的空间和数据内容不变。
所以,我们要对指针做一些处理,如果扩容成功,我们将p指针指向NULL;如果扩容失败,我们不能盲目的将p指针指向NULL,否则的话,我们的空间会无法释放,数据会丢失。

int main()
{
	int* p = (int*)malloc(sizeof(int) * 10);
	int* q = (int*)realloc(p, sizeof(int) * 100);
	if (q == NULL)
	{
		q = p;
	}
	p = NULL;

	free(q);
}

这样写就会解决掉悬挂指针和空间和数据可能会的情况。但是这样做不一定妥当,因为我们不知道是否空间申请成功了,所以我们如果一定要知道是否扩容成功,可以加断言判断等。但是一定要考虑悬挂指针问题,和空间和数据小心丢失问题,就没问题了。

4.3realloc缩小内存的使用

我们在realloc扩容方式中提出可能在原堆空间后面扩容,也可能重新申请堆空间,重新申请堆空间的话,空间首地址会发生改变。在这里不仅有人要想了,那扩容是因为可能寻找新的堆空间才导致空间首地址会发生变化,那么realloc缩小内存,不需要寻找新空间,直接在原来申请的堆空间上将后面的空间释放掉一部分就可以了,那是不是缩小内存的话空间首地址就一定不会改变了?
答案并非如此!
realloc可以在任何调用上移动内存,在许多实现中,收缩只会导致堆中保留大小的更改,并且不会移动内存。然而,在针对低碎片进行优化的堆中,它可以选择将储存器移动到更合适的位置。
所以,对于任何操作,都不要依赖realloc保持内存在同一个地方!
另外,realloc缩小内存的话,如果realloc(void* ptr,unsigned int newsize)中的newsize为0的话,那么ptr指向的空间被释放,realloc返回NULL。如果只是简单的缩小内存,那么原内存中超过newsize大小的空间中的数据会丢失。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

孟小胖_H

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

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

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

打赏作者

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

抵扣说明:

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

余额充值