C语言进阶指南(18)——动态内存管理函数的常见问题

发布时间:2024年01月11日

欢迎来到博主的专栏——C语言进阶指南
博主id已更新,希望大家多多支持新人博主

一、动态内存开辟失败

malloc,calloc,以及realloc用于开辟动态内存时,由于无法再堆区中找到合适的空间区域。此时这些函数会停止开辟内存空间,并返回一个null指针。

我们先来看看一下几段代码

void test1(void)
{
	int* p = malloc(40);
	*p = 10000;
	free(p);
}

void test2(void)
{
	int* p = calloc(1,40);
	*p = 10000;
	free(p);
}

void test3(void)
{
	int* p = realloc(NULL, 40);
	*p = 10000;
	free(p);
}

test1,test2,和test3函数都对开辟的动态内存空间进行了操作,但是前面已经提到了,如果函数开辟动态内存失败了,返回的是一个NULL。也就是说此时p是一个空指针,对空指针进行解引用操作是一个错误的行为

解决方法如下:
对开辟的内存空间进行操作时,先判断这个指针的合法性

bool test1(void)
{
	int* p = malloc(40);
	if(p==NULL)//通过判断p是否为空指针来判断内存开辟是否成功
	return false;
	*p = 10000;
	free(p);
}

现在大家的计算机对于开辟几万,几十万,几百万的个字节的申请简直不值一提。如果大家想见到内存开辟失败,可以使用如下方式

int *p=malloc(INT_MAX);

INT_MAX是一个C语言中的宏,是int类型的数据能得到的最大数值。

二、指针丢失的问题

指针丢失的问题有多种情况

(1)指针指向的动态内存被释放

int* p = malloc(40);
if (p == NULL)
	exit(EXIT_FAILURE);
*p = 10000;
free(p);

p是一个指向动态内存区域的指针,但是当这段区域被释放之后,p仍然指向这片区域,但是此时p已经不再有使用这个区域的权限。也就是说p已经变成了指向不合法内存空间的野指针
在这里插入图片描述
由上图可以发现,由p指向的这片区域已经被释放,这片内存区域已经不再提供指针使用了,而p仍然指向这片区域,所以这个p已然成为一个野指针

解决方案:
请确保自己或者别人不再使用这个指针。

如果不行,可以在指向的动态内存空间被释放之后,将这个指针置为空指针(NULL)。

int* p = malloc(40);
if (p == NULL)
	exit(EXIT_FAILURE);
*p = 10000;
free(p);
p = NULL;

(2)指针被修改导致的丢失动态内存空间

这个问题主要在使用realloc函数修改内存空间是遇见。由于realloc可以用来修改动态内存空间的位置,使用realloc函数时会遇到以下情况(包括但不唯一)

(a)调整空间的请求是合理的,申请通过,realloc返回调整空间后的起始地址。
(b)动态内存区域开辟失败,返回NULL。

为了省事,有人会用同一个指针来接收这个函数的返回值,因为如果这个函数修改成功了,这个指针是可以继续指向一个有效的动态内存空间的

int* p = malloc(40);
p = realloc(p, 80);

在这里插入图片描述
使用同一个指针,使其继续保持指向重新开辟的区域

但是这种风格会导致一个问题,就是当realloc函数开辟动态内存失败时,返回的是一个NULL值,这就会导致这种情况

在这里插入图片描述

realloc函数开辟新区域后,会将原区域的数据拷贝进新区域,并将原区域的内存释放。
而realloc函数开辟新区域失败时,原区域将会被保留

于是原区域的内存,此时没有任何一个指针指向这片区域了。这就导致我们永远的丢失了这片区域,这片区域不会再开辟(因为已经具有权限),又无法将其释放(没有指针指向),
且不能使用(没有可以操作这片区域的指针)。

解决方法如下:
使用一个新指针来指向这个地方。

int* p1 = malloc(40);
int* p2 = realloc(p1, 80);

如果觉得使用多个指针会影响程序的可读性的话,可以用一个临时指针来替代。

int* p1 = malloc(40);
int* p2 = realloc(p1, 80);
if (p2 != NULL)
{
	p1 = p2;
}

这样子在后续的程序当中,可以继续使用p1来指向重新调整的区域.

(3)超出指针的作用域,使得指针丢失指向区域

在C语言中,除了在静态区的变量以外的变量,都有其作用域,通常这个作用域在花括号以内**“{}”**

有时候因为项目的需要,一个动态开辟的区域不会在创建过后立即销毁,而是在后续执行某个操作之后,再将这个这个动态内存释放掉。

void getmemory(void)
{
	int* p = malloc(40);
	if (p == NULL)
		exit(EXIT_FAILURE);
	*p = 10000;
}

这个创建动态内存的函数具有一个问题,那就是当这个函数执行完成之后,指针由于超出作用域,被计算机系统释放掉。那么这片内存区域就不再具有指针指向了,后续的操作也无法使用到这片区域。

在这里插入图片描述
解决方式:

int* p = malloc(40);
if (p == NULL)
	exit(EXIT_FAILURE);
*p = 10000;
return p;

虽然这个指针超出了作用域被释放了,但是函数结束后将这个地址传回,创建一个新的指针来接收这个地址,这个地址可以留着供自己或他人使用,并且可以被释放。

三、realooc函数可能发生的多种情况

realloc根据内存的分配状态有着不同的调整内存的方式,以一个代码为例

int* p = malloc(40);
int* ptr = realloc(p, 80);

假设malloc开辟40字节大内存大小之后,返回给指针p的值为0x11223344,也就是这片动态内存的区域的起始地址为0x11223344.

在大部分程序中,不会仅仅只有一个部分的内存区域被开辟的。

也就是说这个代码在内存中可能会产生的内存区域为
在这里插入图片描述
(a)假如重新开辟的区域不会与其他的内存区域重合。

也就是说p指向的动态内存空间,在经过realloc函数重新调整之后,不会与粉色的内存空间发生重合。
在这里插入图片描述

那么此时realloc返回的地址值与p是一致的,也就是0x11223344.
(b)如果重新调整后的区域的后续区域重合
在这里插入图片描述
这种情况下,realloc函数会在内存的其他区域当中,找到一个存储空间合适的位置,将原区域(0x11223344)的数据拷贝至新区域,并将原区域的内存释放出去。realloc函数返回新区域的起始地址。
在这里插入图片描述
需要注意的是,虽然原区域被释放了,而且realloc函数的返回值也不再是原区域的起始地址,但是这不会修改p,也就是p仍然指向原区域(0x11223344),即使它已经被释放了。
(c)内存中找不到任一区域来存放新的地址
如果在内存当中,函数找不到一处合适的空间来符合调整后的字节需求,那么这时候realloc函数不会在内存中开辟新的空间,函数返回值为空指针NULL,原空间不会被修改。

文章来源:https://blog.csdn.net/2301_77239666/article/details/135307752
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。