c语言动态内存管理

系列文章目录

第十七章 联合体和枚举


文章目录


前言

目前我们所掌握的内存开辟方式能开辟的空间大小是固定的,而且数组声明的时候要指定数组长度,数组空间一旦确定了大小不能调整。但是在生活中我们常常会遇到曾经开辟的空间不够用的情况,我们有在原空间的基础上开辟更多空间的需求,因此C语言引入了动态内存开辟,我们可以自由的申请和释放空间。


一、malloc和free

1.1、malloc

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

void* malloc (size_t size);

这个函数会向内存申请一块连续可用的空间,并且返回这块空间的指针,这里分为两种情况:

1,如果开辟成功,则返回这块空间的指针。

2,如果开辟失败,则返回一个NULL指针。

因此我们在使用malloc函数的时候一定要对其返回值做检查。返回值类型是void* ,所以malloc并不知道开辟空间的类型,具体在使用的时候由使用者自己决定。如果参数size为0,malloc的行为标准是未定义的,取决于编译器。

1.2、free

free函数是专门用来做动态内存的释放和回收的,原型如下:

void free(void* ptr);

free函数用于释放动态开辟的内存,如果参数ptr指向的空间不是动态开辟的,那么free函数的行为是未定义的。如果ptr是NULL函数,则函数什么事的不做。

malloc函数和free函数的声明都在stdlib头文件中,使用的时候需要包含。

下面我们写一个简单的代码来展示其用法:

c 复制代码
#include<stdio.h>
#include<stdlib.h>

int main() {
	int num = 0;
	scanf("%d", &num);
	int* ptr = NULL;
	ptr = (int*)malloc(num * sizeof(int));
	if (NULL != ptr) {
		int i = 0;
		for (i = 0; i < num; i++) {
			*(ptr + i) = 0;
		}
	}
	free(ptr);
	ptr = NULL;
	return 0;
}

我们在使用free将内存释放掉之后一定要记得把指针置空,这么做有两个好处,一是内存在free之后,指针若不置空则还是指向原来内存地址,便是野指针,若继续调用会发生未定义行为,可能会导致程序崩溃。二是防止内存被释放两次。

二、calloc和realloc

2.1、calloc

C语言提供了一个叫做calloc的函数,也是用于动态内存分配,原型如下:

void* calloc(size_t num, size_t size);

他与malloc的区别是他在开辟完空间之后会将空间的每个字节先初始化成0,再返回地址。

2.2 realloc

realloc函数的出现让内存动态管理更加灵活,他可以对动态开辟的内存大小进行调整。

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

ptr是要调整的内存的地址,size是调整的大小,返回值是新空间的地址,他在调整原内存空间大小的基础上还会将原来内存中的数据移动到新的空间。

realloc调整内存空间有两种情况:

1,原有空间之后有足够大的空间:这种时候要扩展内存就直接在原有内存空间之后直接追加空间,原来空间的数据不发生变化。

2,原空间之后没有足够的空间:这种情况就在堆空间上另外寻找一个大小合适的连续空间使用,这样会返回一个新的空间的地址。

同时在若是realloc申请空间失败会返回NULL,因此当我们在使用realloc的时候我们应该要对其返回值进行判断。

三、柔性数组

这是C99中存在的一个概念,结构中的最后一个元素允许是未知大小的数组,就叫做柔性数组的成员

c 复制代码
	struct st_type{
		int i;
		int a[0];//或者int a[];
	}

结构中的柔性数组成员前面必须至少一个其他成员,sizeof返回这种结构大小不包含柔性数组的内存,包含柔性数组的成员结构用malloc()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,用于适应柔性数组的预期大小。

c 复制代码
typedef struct st_stype {
	int i;
	int a[0];
}type_a;
int main() {
	printf("%d\n", sizeof(type_a));//这里输出4
	return 0;
}

下面给一个使用的例子:

c 复制代码
int main() {
	int i = 0;
	type_a* p = (type_a*)malloc(sizeof(type_a) + 100 * sizeof(int));
	p->i = 100;
	for (int i = 0; i < 100; i++) {
		p->a[i] = i;
	}
	free(p);
	return 0;
}

四、题目练习

为什么这段代码会报错?

这是指针传值导致的坑,在这里GetMemory函数传递的是str的一份值拷贝,也就是说str只是值和p一样都是NULL,但两个其实是两个变量,当p指向新开辟的内存的时候str仍然没有改变,最后strcpy对str进行了空值解引用,同时因为p销毁开辟的空间找不到了,也引发了内存泄漏。

解决方法有两个,一个是传二级指针,这样p指向的就是str的地址

第二种方法就是用返回值接收一下开辟的空间的地址,这种方法更加简单易懂。

为什么这段代码输出的结果是乱码?

这里主要涉及函数栈帧,函数中的p以及所指向的的空间在出作用域的时候就已经被销毁,所以str指向的地方打印出来的结果就是随机值。

解决办法就是直接返回字符串常量或者用静态变量,或者在堆上开辟内存,这里个人认为直接用返回字符串常量是最好的。首先堆上动态开辟之后还要记得free,要是忘记会造成内存泄漏,很麻烦,其次是静态变量,这是个大坑,它不可重入、线程不安全:所有调用共享同一块静态内存,上一次调用的结果会被下一次调用覆盖;多线程环境下会出现数据竞争。只能用于临时使用结果的场景,不能长期保存、不能多处同时使用。所以不推荐使用。


总结

本文系统介绍了 C 语言动态内存管理的核心函数与概念。mallocfree 是动态内存申请与释放的基础,使用时需注意检查返回值、及时释放并置空指针,避免内存泄漏与野指针问题。calloc 在分配内存的同时自动初始化为零,适合需要清零的场景。realloc 则提供了灵活调整已分配内存大小的能力,使用时需注意其可能返回新地址及失败情况。柔性数组作为 C99 特性,允许在结构体末尾定义未知大小的数组,需配合 malloc 进行动态分配。最后,通过两道典型题目练习,我们深入理解了指针传值、函数栈帧对动态内存的影响,并探讨了返回字符串常量、静态变量与堆内存的适用场景与陷阱。掌握这些知识,能够帮助我们在实际编程中更安全、高效地使用动态内存,写出更健壮的 C 语言程序。

相关推荐
牛阿大1 小时前
MPC算法
算法
祖力551 小时前
快速学会数据结构中的链式队列与循环队列
linux·c语言·数据结构·算法
冰暮流星1 小时前
javascript之webstorage用法
开发语言·javascript·ecmascript
我一定会有钱1 小时前
打造完美工作流:Git 配置”一次推送,三端同步”(Gitee/GitCode/GitHub)
开发语言·windows·vscode·搜索引擎·gitlab·github·visual studio code
原凉是这样的丶1 小时前
Qwen3.8-27B 开源后,我用双 RTX 4090 跑通了:vLLM 0.27.1、128K 上下文与 MTP 实测
linux·算法
matlab代码1 小时前
基于matlab肤色人脸标记检测系统 GUI界面【源码53期】
开发语言·matlab·人脸标记
KhalilRuan2 小时前
UnityCsReference——笔记
java·开发语言·笔记
电子云与长程纠缠2 小时前
UE5 Lyra PocketWorld进行3D内容UI预览 - 上
开发语言·学习·3d·ue5·游戏引擎
码农大叔的博客2 小时前
golang示例:for九九乘法表
开发语言·算法·golang