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 语言程序。

相关推荐
点心的游戏开发世界3 小时前
GDScript 入门笔记(十一):Lambda 与匿名函数
开发语言·笔记·游戏引擎·godot
phltxy3 小时前
C语言操作符详解
java·c语言·算法
RuoZoe3 小时前
从 2026 年 3 月 1 日开源,到 26.10.9:Jalium UI 半年时间到底走了多远?
c语言·c++
aqiu1111113 小时前
【LeetCode 902】最大为 N 的数字组合 - 详细题解与数位组合思路
数据结构·算法·leetcode·蓝桥杯·数位dp
辰烨chenye4 小时前
LeetCode Hot 100 题解 · 哈希篇
算法·leetcode·哈希算法
事圆则缓5 小时前
Kotlin 协程完全指南
开发语言·kotlin
罗西的思考5 小时前
DreamZero 与 DreamDojo:世界模型与策略的分层协同综合分析与对比
人工智能·算法·机器学习
我爱写代码i5 小时前
BeLink - 支持生成多种URL 缩短网址PHP源码
开发语言·php·短网址php源码
Java后端的Ai之路5 小时前
20、Python - 备忘录模式
开发语言·人工智能·python·外观模式·备忘录模式