【C语言】动态内存管理全解析:malloc、calloc、realloc与free的正确使用

发布于:2025-07-20 ⋅ 阅读:(13) ⋅ 点赞:(0)

C语言学习

动态内存分配
友情链接:C语言专栏



前言:

在C语言编程中,内存管理是开发者必须掌握的核心技能之一。静态内存分配虽然简单易用,但在实际开发中往往无法满足灵活多变的内存需求。动态内存分配技术为程序提供了运行时按需分配内存的能力,极大地增强了程序的灵活性和资源利用率。本文将深入讲解C语言中动态内存分配的四大关键函数:malloc、calloc、realloc和free,通过原理分析、代码示例和常见问题解析,帮助读者全面掌握动态内存管理的精髓,避免内存泄漏和野指针等常见问题。


一、为什么要有动态内存分配

咱们先来看一下咱们已经掌握的内存开辟方式有:

	int val = 10;//在栈空间上开辟四个字节
	char arr[10] = { 0 };//在栈空间上开辟10个字节的连续空间

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

空间开辟大小是固定的。
数组在申明的时候,必须指定数组的长度,数组空间⼀旦确定了大小不能调整

但是我们在写代码的时候对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟空间的方式就不能满足了。
C语言 引入了动态内存开辟,让程序员自己可以申请和释放空间,就比较灵活了。

二、malloc和free

2.1 malloc

C语言提供了⼀个动态内存开辟的函数:

void* malloc (size_t size);

这个函数向内存申请⼀块连续可用的空间,并返回指向这块空间的指针。

  • 如果开辟成功,则返回⼀个指向开辟好空间的指针。
  • 如果开辟失败,则返回⼀个 NULL 指针,因此malloc的返回值⼀定要做检查。
  • 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
  • 如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。

2.2 free

C语言提供了另外一个函数free,专门是用来做动态内存的释放和回收的(动态申请的内存如果不再使用,必须显式地释放并归还给操作系统),函数原型如下:

void free (void* ptr);

free函数用来释放动态开辟的内存。

  • 如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
  • 如果参数 ptr 是NULL指针,则函数什么事都不做。

malloc和free都声明在 stdlib.h 头文件中。

代码示例:

#include <stdio.h>
#include <stdlib.h>
int main()
{
	int* ptr = NULL;
	//开辟
	ptr = (int*)malloc(1000000000000);//动态开辟20个字节的内存
    if(ptr == NULL)//判断ptr指针是否为空(是否开辟成功)
	{
		perror("malloc:");
	}
	//使用
	else
	{
		int i = 0;
		for (i = 0; i < 5; i++)
		{
			*(ptr + i) = 0;
		}
	}
	//释放
	free(ptr);//释放ptr所指向的动态内存,传入的指针必须是要释放的内存空间的起始地址。
	//注意:free只是将这一块内存还给操作系统了,而对于ptr指针未作任何改变
	//此时,ptr就是野指针
	ptr = NULL;//即将野指针置空
	return 0;
}

三、calloc和realloc

3.1 calloc

C语言还提供了一个函数叫calloccalloc 函数也用来动态内存分配。原型如下:

void* calloc (size_t num, size_t size);

参数解释:

为 num 个大小为 size 的元素开辟⼀块空间,并且把空间的每个字节初始化为0。

与函数 malloc 的区别:

只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0

举个例子:

#include <stdio.h>
#include <stdlib.h>
int main()
{
	int* p = (int*)calloc(10, sizeof(int));//开辟10个空间大小为int的内存空间
	if (NULL != p)//判断p指针是否为空(是否开辟成功)
	{
		int i = 0;
		for (i = 0; i < 10; i++)
		{
			//咱们并未主动赋值
			//直接打印
			printf("%d ", *(p + i));
		}
	}
	free(p);
	p = NULL;
	return 0;
}

输出:
在这里插入图片描述
所以如果我们对申请的内存空间的内容要求初始化,那么可以很⽅便的使⽤calloc函数来完成任务。

3.2 realloc

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

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

参数解释:

ptr 是要调整的内存地址
size 调整之后新大小
返回值为调整之后的内存起始位置。

那咱们就会有疑问,为什么返回值是调整之后的内存起始位置,起始位置不是一直没变吗,其实不是这样的,
realloc在调整内存空间的是存在两种情况:

  • 情况1:原有空间之后有足够够大的空间
  • 情况2:原有空间之后没有足够大的空间

图示:
在这里插入图片描述
情况1:
当是情况1 的时候,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发⽣变化。
情况2:
当是情况2 的时候,原有空间之后没有大小够多的空间时,扩展的方法是:在堆空间上另找⼀个合适大小的连续空间来使用。这样函数返回的是⼀个新的内存地址。
情况2图示:
在这里插入图片描述
知道了上述的两种情况,realloc函数的使用我们就要注意⼀些要点了。
看代码:

#include <stdio.h>
#include <stdlib.h>
int main()
{
	int* ptr = (int*)malloc(100);
	if (ptr != NULL)
	{
		//……
	}
	else
	{
		perror("malloc");
	}

	//扩展容量为1000个字节
    //完善代码
    //……
	return 0;
}

那咱们可以这样写吗:

	ptr = (int*)realloc(ptr, 1000);

不行,坚决不允许。
解释:

直接将realloc的返回值放到ptr中的话,如果realloc函数申请失败则会返回NULL,此时会使得ptr为NULL。
就是会导致:

  1. 内存泄漏(原内存无法释放)
  2. 无法访问原有数据

正确怎么写呢?
我们先将realloc函数的返回值放在p中,当p不为NULL,再放ptr中:

	int* p = NULL;
	p = (int*)realloc(ptr, 1000);
	if (p != NULL)
	{
		ptr = p;
	}

这样是没有问题的。
那怎么把上面代码总结一下,完整的如下:

#include <stdio.h>
#include <stdlib.h>
int main()
{
	int* ptr = (int*)malloc(100);
	if (ptr != NULL)
	{
		//使用……
	}
	else
	{
		perror("malloc");
		return 1; // 直接退出,避免后续操作
	}

	//扩展容量
	//先将realloc函数的返回值放在p中,不为NULL,在放ptr中
	int* p = NULL;
	p = (int*)realloc(ptr, 1000);
	if (p != NULL)
	{
		ptr = p;
	}

	//使用……
	 
	//释放
	free(ptr);
	ptr = NULL;// 防止野指针(良好习惯)

	return 0;
}

所以,咱们要知道:
malloc、calloc 和 realloc 在内存分配失败时都会返回 NULL。


总结

良好的内存管理习惯不仅能避免程序崩溃和内存泄漏,还能提高代码的健壮性和可维护性。记住:动态分配的内存就像借来的书,用完后一定要记得归还(释放)!
对于这一部分内容,不难,但是容易出错,后续我会关于易错点等等再出一篇文章,帮助大家理解,谢谢观看至此!!!

附录

上文链接

《联合体完全指南:内存共享、大小计算与实战应用》

下文链接

《动态内存分配避坑指南:六大易错点解析与经典笔试题实战》

专栏

C语言专栏


网站公告

今日签到

点亮在社区的每一天
去签到