【C语言】动态内存管理详细解析

前言:动态内存分配与管理是C语言中非常重要的部分,它允许我们根据需要申请或者释放指定大小的内存空间供我们使用,与在栈区存放的局部变量、函数参数等等相比会更加的灵活


1.动态内存的常用函数

动态内存函数顾名思义就是可以用于操作动态内存,接下来我们的一系统操作都建立在它们之上,所以学习它们至关重要,动态内存函数在使用之前都要包含头文件<stdlib.h>


1.1malloc函数

这个函数可以向堆区内存申请一块连续的内存,并返回开始申请内存时的起始地址,函数的在官网介绍的原型为:

可以看到的是这个函数会返回一个无类型的指针,因为这个函数不知道我们要操作的是哪个类型的数据,所以在接受返回值的时候我们可以将其强制转化成我们需要的类型,下面我将以具体的代码为例子来介绍该怎么使用:

int main()
{
	//申请16个字节的空间
	int* p = (int*)malloc(16);
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	//p返回开辟空间的起始地址
	for (int i = 0; i < 4; i++)
	{
		*(p + i) = i + 1;
	}
	for (int i = 0; i < 4; i++)
	{
		printf("%d ", *(p + i));
	}
	//释放空间
	free(p);
	p = NULL;
	return 0;
}

运行的结果为:

malloc后面的括号里的数字表示的是以字节为单位在内存中开辟的空间,这里我需要在内存中存入四个整形变量,所以我申请了16个字节的大小,并将返回的无类型指针强制转化成了整形类型的指针存入一个整形类型的指针中。

当这个函数开辟内存失败时,就会返回一个空指针NULL所以后面我用了一个if语句来判断是否为空指针,是的会就通过perror函数打印出错误信息并提前结束程序,这是一个良好的书写习惯,可以在我们要申请的内存比较小时没什么感觉,但当要申请的内存非常大时就会导致程序有可能崩溃,比如申请(INT_MAX)时就会导致程序崩溃。

这个开辟的空间是连续的所以我们可以想象成数组:

但是需要注意的是当我们在括号中输入0个字节时,这个操作是未定义的,不同的编译器会有不同的结果,但这个操作太奇怪了吧(,既然要用这个函数申请空间的话为什么又只填个0,相信不会有人这个干吧哈哈


1.2free函数

在上面代码中的结尾部分,我使用了free函数并传入了p的地址。这个函数的作用是用于回收和释放动态内存的,在上面举例的代码中,我向动态内存中申请了空间并完成了输入与打印数据的任务,当任务完成时我们需要将这块内存释放掉,否则当内存一直被占用时,在一些场景下会造成内存泄漏的问题

但是需要注意的是就算申请的内存空间被销毁了,原本指向这块空间的指针变量p也会变成野指针,所以为了避免这个问题我们需要在free(p)的后面加上 p = NULL,来避免野指针带来的风险

free(NULL)这个操作是合法的,但是这样的话这个函数就会什么也不做


1.3calloc函数

这个函数和malloc函数几乎一致,但是它会将申请到的空间初始化为0。

int main()
{
	int* p = (int*)calloc(4, sizeof(int));
	//会初始化为零
	if (p == NULL)
	{
		return 1;
	}
	for (int i = 0; i < 4; i++)
	{
		printf("%d ", *(p + i));
	}
	free(p);
	p == NULL;
	return 0;
}

打印的结果为:

可以看到我申请的4个整形大小的空间都被初始化为0,当我们想要初始化时这个函数可以很方便的完成任务。


1.4realloc函数

有时当申请的内存过大或者小时,我们就可以使用这个函数来调整申请到的动态内存的空间大小,它可以使得我们可以更加灵活的管理内存,函数原型如下:

前面的指针变量ptr用于接受要更改内存大小的动态内存的地址,后面的size则是更改后的内存空间大小,当更改内存失败时和前面一样会返回一个空指针NULL。下面我们以一个代码来举例介绍这个函数的使用方法:

int main()
{
	int* p = malloc(4 * sizeof(int));
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	for (int i = 0; i < 4; i++)
	{
		*(p + i) = i + 1;
	}
	int* ptr = realloc(p, 32);
	if (ptr != NULL)
	{
		p = ptr;
	}
	else
	{
		perror("realloc");
		return 1;
	}
	return 0;
}

这里我使用了p指针来接受返回realloc更改过的内存地址,因为我希望p指针继续管理内存,这时会产生一个疑问,我不是传入了p指针吗?难道不是对原来p指向的空间进行内存大小的更改吗?下面来介绍使用realloc函数可能会遇到的三种情况:

第3种情况下就是当调整失败时返回一个NULL,这个比较简单我就不画出来了。所以地址是有可能和原地址p有所不同的,当然也有可能返回NULL,所以在接受返回指针时要先判断一下是否为空指针。

realloc也是可以实现和malloc一样功能的,下来我来举一个例子:

#include <stdlib.h>
int main()
{
	realloc(NULL, 20);//malloc(20)
	return 0;
}

这个了解一下就行了,一般还是使用malloc就好了


2.常见错误举例

2.1对NULL解引用

int main()
{
    int* p = (int*)malloc(INT_MAX);
    *p = 20;
    return 0;
}

因为需要申请的空间太大了malloc会返回一个空指针,而我们不能对空指针p进行解引用操作否则,这就是为什么我们都要判断是否为空指针的原因


2.2对非动态空间使用free函数

int main()
{
	int a = 10;
	int* p = &a;
	//错误写法,free只能释放在堆区申请的空间
	free(p);
	p = NULL;
	return 0;
}

free函数的作用前期一定要是申请到的动态内存空间,否则程序同样会崩溃


2.3对同一块内存空间重复释放

void test()
{
    int* p = (int*)malloc(100);
  
    free(p);
    free(p);//重复释放
}


int main()
{
    test();
    return 0;
}

为了防止这么情况,我们可以在每次用free释放完p指向的空间时将p赋值为空指针,这样free操作就是无效了:

int main()
{
	int* p = (int*)malloc(20);
	
	free(p);
	p = NULL;
	free(p);
	return 0;
}

2.4内存泄漏

内存泄漏发生的原因多半是因为忘记了将申请的空间用free函数释放掉,如果是循环场景下的话情况会更加糟糕,一直申请内存但是又不全释放,最后当内存被占满时可能会导致程序崩溃,来看看下面这种尴尬的情况:

void test()
{
	int* p = (int*)malloc(20);
	if (p != NULL)
	{
		*p = 12;
	}
}
int main()
{
	test();
	while (1);
	return 0;
}

这段程序向内存中申请了20个字节的空间,但是在完成test()函数时并没有对这个空间进行回收,更加尴尬的是当程序走完test()函数时因为指针p变量的销毁,我们甚至找不到指向这块空间的地址进行补救。。。

还比如有:

void test()
{
    int* p = (int*)malloc(20);
    int i = 2;
    if (i > 0)
        return;
    free(p);
    p = NULL;
}

int main()
{
    test();
    while (1);
}

当提前返回时,就会提前结束这段程序,并没有执行free这也同样需要注意一下。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值