C语言:动态内存管理

动态内存管理

一、动态分配内存的必要性

普通内存分配

  1. 空间开辟大小是固定的。
  2. 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配。

动态内存分配

1、空间大小可以更改。

2、空间大小可以在程序执行时再分配。

二、动态内存分配函数

(一)malloc

/ 复制代码
void* malloc (size_t size);

使用规则:

传入字节数,申请成功返回申请空间的首地址,申请失败返回空地址,因此申请空间需要进行检查。

/ 复制代码
int main() {
	int* p = (int*)malloc(10 * sizeof(int));
	if (p == NULL) {             // 进行检查会更安全
		perror("malloc");
		return 1;
	}
	printf("申请成功");
	free(p);                      //后面会有函数用法
	p = NULL;
	return 0;
}

(二)calloc

/ 复制代码
void* calloc (size_t num, size_t size);

使用规则:

传入申请类型个数以及一个类型的字节数,申请成功返回申请空间的首地址,申请失败返回空地址,申请空间需要进行检查。

与 malloc 不同的是calloc 会初始化每个字节为0;

/ 复制代码
int main() {
	int* p = (int*)calloc(10, sizeof(int));
	if (p == NULL) {
		perror("calloc");
		return 1;
	}
	printf("申请成功");
	free(p);
	p = NULL;
	return 0;
}

(三)realloc

/ 复制代码
void* realloc (void* ptr, size_t size);

使用规则:

如果原来的内存块后面的空间足够开辟空间,那么直接在内存块后面扩展内存。否则 realloc 会另外寻找一片内存进行开辟,返回找到的地址,如果实在找不到这么大的内存,就会返回NULL。

/ 复制代码
int main() {
	int* arr = (int*)malloc(sizeof(int) * 10);
	if (arr == NULL) {
		perror("malloc\n");
		return 1;
	}
	int* p = (int*)realloc(arr, sizeof(int) * 20);
	if (p == NULL) {
		perror("realloc\n");
		return 1;
	}
	arr = p;
	free(arr);
	arr = NULL;
	return 0;
}

如果 ptr 传入NULL,他的效果就相当于 malloc。

/ 复制代码
int main() {
	int* p = (int*)realloc(NULL, sizeof(int) * 20);
	if (p == NULL) {
		perror("realloc\n");
	}
	free(p);
	p = NULL;
	return 0;
}

(四)free

/ 复制代码
void free (void* ptr);

上面的函数 malloc calloc realloc 分配了空间之后,也需要进行释放。如果不释放,可能导致内存泄漏。如果不置空,可能导致非法访问。

如果传入NULL,就不进行操作。

三、常见的错误

(一)对空指针进行解引用操作

如果分配空间时,内存不足以开辟返回NULL;不进行检查就解引用可能导致程序崩溃。

(二)对动态分配空间越界访问

顾名思义,就是不可以越界访问。

(三)free释放动态分配空间的一部分

如下,p++ 改变了 p 指向的内存,不再是首地址,就好像是释放一部分空间,这种情况程序会崩溃。

/ 复制代码
int main() {
	int* p = (int*)malloc(10*sizeof(int));
	if (p == NULL) {
		perror("malloc");
		return 1;
	}
	for (int i = 0; i < 5; i++) {
		*(p++) = i;
	}
	free(p);
	p = NULL;
	return 0;
}

(四)动态开辟内存忘记释放

下面代码中,如果开辟成功,malloc 开辟的内存没有释放,但是 p 的地址我们在函数外面就再也找不到了,这就是内存泄漏。

/ 复制代码
void test()
{
	int flag = 1;
	int*p = (int*)malloc(100);
	if (p == NULL)
	{
		return;
	}
	if (flag)
		return;
	free(p);
	p = NULL;
}

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

四、柔性数组

(一)柔性数组的特点

1、结构中的柔性数组成员前面必须至少一个其他成员。

2、sizeof 返回的这种结构大小不包括柔性数组的内存。

3、包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。

/ 复制代码
struct S {
	int n; 
	int arr[];
};

int main() {
	struct S* p = (struct S*)malloc(sizeof(struct S) + sizeof(int) * 10);
	if (p == NULL) {
		perror("malloc");
	}
	struct S* temp = (struct S*)realloc(p, sizeof(struct S) + sizeof(int) * 20);
	if (temp == NULL) {
		perror("realloc");
	}
	p = temp;
	return 0;
}

(二)柔性数组的优势

相比于上面的代码,肯定也会有疑惑,为什么不在结果体中设置一个指针变量进行动态内存开辟空间。

那我们就进行探究

/ 复制代码
struct S
{
	int n;
	int* arr;
};

int main()
{
	struct S* ps = (struct S*)malloc(sizeof(struct S));
	if (ps == NULL)
	{
		perror("malloc");
		return 1;
	}
	int*tmp = (int*)malloc(20*sizeof(int));
	if (tmp != NULL)
	{
		ps->arr = tmp;
	}
	else
	{
		return 1;
	}
	tmp = (int*)realloc(ps->arr, 40*sizeof(int));
	if (tmp != NULL)
	{
		ps->arr = tmp;
	}
	else
	{
		perror("realloc");
		return 1;
	}
	free(ps->arr);
	ps->arr = NULL;
	free(ps);
	ps = NULL;
	return 0;
}

我们发现柔性数组的优势:
1、提高访问速度:

连续的内存有益于提高访问速度,也有益于减少内存碎片
2、方便内存释放:

如果用动态内存开辟的方式,我们要释放两次才能释放掉内存空间,因此柔性数组更优秀。

五、有趣的题目

1、求下面代码的输出结果

/ 复制代码
void GetMemory(char* p)
{
    p = (char*)malloc(100);
}

void Test(void)
{
    char* str = NULL;
    GetMemory(str);
    strcpy(str, "hello world");
    printf(str);
}

这个代码有两个问题:

1、 传入函数中的为NULL,动态分配空间GetMemory 函数结束后就无法访问到了,造成内存泄漏。

2、strcpy 函数中,如果对空指针进行解引用操作,导致程序崩溃。

可查看strcpy详解

2、求下面代码的输出结果

/ 复制代码
int main() {
	char* p = (char*)malloc(sizeof(int) * 10);
	strcpy(p, "hello");
	free(p);         //这里没有及时的将 p 置空造成了野指针
	if (p) {         //发生非法访问
		strcpy(p, "what can i see!");
	}
	return 0;

根据代码中分析,这段代码在运行时也会导致程序崩溃。

六、结束语

小伙伴看了这篇文章感觉如何呢,如果能有收获是值得高兴的,如果觉得有帮助的化,别忘记点赞支持支持哦!

相关推荐
清梦202038 分钟前
经典问题---跳跃游戏II(贪心算法)
算法·游戏·贪心算法
Dream_Snowar1 小时前
速通Python 第四节——函数
开发语言·python·算法
Altair澳汰尔1 小时前
数据分析和AI丨知识图谱,AI革命中数据集成和模型构建的关键推动者
人工智能·算法·机器学习·数据分析·知识图谱
嵌入式科普2 小时前
十一、从0开始卷出一个新项目之瑞萨RA6M5串口DTC接收不定长
c语言·stm32·cubeide·e2studio·ra6m5·dma接收不定长
A懿轩A2 小时前
C/C++ 数据结构与算法【栈和队列】 栈+队列详细解析【日常学习,考研必备】带图+详细代码
c语言·数据结构·c++·学习·考研·算法·栈和队列
Python机器学习AI2 小时前
分类模型的预测概率解读:3D概率分布可视化的直观呈现
算法·机器学习·分类
吕小明么3 小时前
OpenAI o3 “震撼” 发布后回归技术本身的审视与进一步思考
人工智能·深度学习·算法·aigc·agi
1 9 J3 小时前
数据结构 C/C++(实验五:图)
c语言·数据结构·c++·学习·算法
程序员shen1616113 小时前
抖音短视频saas矩阵源码系统开发所需掌握的技术
java·前端·数据库·python·算法
汝即来归3 小时前
选择排序和冒泡排序;MySQL架构
数据结构·算法·排序算法