动态内存管理<C语言>
创始人
2025-01-15 16:36:07
0

导言

        在C语言学习阶段,指针结构体动态内存管理,是后期学习数据结构的最重要的三大知识模块,也是C语言比较难的知识模块,但是“天下无难事”,只要认真踏实的学习,也能解决,所以下文将介绍动态内存管理涉及到的一些函数以及概念。


目录

导言

为什么存在动态内存管理

malloc和free

malloc

 free

calloc和realloc

calloc

 realloc

常见的关于动态内存管理错误

1.对可能是NULL指针的引用

 2.对不是动态开辟的内存进行释放

3.对动态开辟的内存进行越界访问

4.使用free释放动态开辟内存的一部分

5.忘记内存释放(忘记free),造成内存泄漏

例题


为什么存在动态内存管理

int a; char arr[10];

这是我们常用的用于向内存申请空间的办法,但是:

●空间开辟的空间是固定的

●数组在申明时,数组大小一旦确定,申请的内存空间不可变

在实际编写程序时,可能我们对于内存空间的需求不是固定,那么使用动态内存管理自己申请空间、自己释放空间就是一个很好的选择。


malloc和free

malloc

函数参数及其返回值

void* malloc(size_t size); //申请size个字节的空间 //返回值,成功申请:返回开辟空间的首地址、失败:返回NULL

注意点

●返回值是void*,那么我们在实际使用时,应把它强制转化为我们需要的类型。

●与局部变量不同,开辟空间在堆区(如数组在栈区)

●malloc不会将内存空间初始化为0,这是与最大calloc区别!

●动态内存可调整(通过realloc)

使用举例:

 free

函数参数及其返回值

void free(void* ptr); //释放动态内存申请的ptr指向的空间

注意点

●只能用来手动释放动态申请的空间,如果不是结果是未定义的

●释放空间后,只是将权限交还于操作系统,指针还指向着地址(悬空指针),应该手动将其置为NULL

●如果释放指针是NULL,那么什么也不做。

 使用举例:

#include int main() { 	int* ptr = NULL; 	int count = 0; 	scanf("%d", &count); 	ptr = (int*)malloc(count * sizeof(int)); 	free(ptr); 	ptr = NULL; 	return 0; }

calloc和realloc

calloc

函数参数及其返回值

void* calloc(size_t num,size_t size); //申请num个size个字节的空间,并初始化为0 //返回值,成功申请:返回开辟空间的首地址、失败:返回NULL

注意点

●开辟空间并全部初始化为0

●两个参数(num个size字节)

●其他与malloc类似

使用举例:

 realloc

 函数参数及其返回值

void* realloc(void* ptr,size_t size); //ptr是要调整的内存地址 //size是调整之后的大小 //返回值,成功申请:返回调整空间的首地址、失败:返回NULL

使用举例:

#include #include int main() { 	int count; 	scanf("%d", &count); 	int* ptr = (int*)calloc(count, sizeof(int));//申请count个int大小的空间 	if (ptr) {//判断是不是NULL:是否申请成功 		for (int i = 0; i < count; i++) 			ptr[i] = i;//赋值:从0开始到count-1步为1的序列 		for (int i = 0; i < count; i++) 			printf("%d ", ptr[i]); 	} 	printf("\n"); 	printf("调整前的地址:%p\n", ptr);//观察动态(realloc)调整前的地址 	int* p = (int*)realloc(ptr, (count + 5) * sizeof(int)); 	//申明一个新指针来接收,防止调整失败返回NULL,数据丢失,调整为多5个int大小的地址 	if (p)//判断是否是NULL:是否调整成功 		ptr = p; 	printf("调整后的地址:%p", ptr);//观察动态(realloc)调整后的地址 	free(ptr); 	ptr = NULL; 	return 0; }

运行结果:

先开辟10个int字节大小空间的运行结果:

 先开辟20个int字节大小空间的运行结果:

注意点

●参数size为0时,返回值是NULL,并将ptr的内存释放,这是未定义的行为,在不同的编译器上不能保证

●如果ptr参数为NULL,会动态开辟一个新的内存空间,此时realloc函数的作用等同于malloc

●这个函数调整空间时会把数据移到的内存空间内(实际上,有一种情况不会,但是为了代码的健壮性和可移植性,我们最好这样定义)

两种情况:

①原有地址后面有足够的空间容纳调整后的空间

②原有地址后面没有足够的空间容纳调整后的空间

其实在前面的使用举例中我们已经观察到:

先开辟10个int字节大小空间的运行结果(第一种情况)

        直接在原地址后面开辟新空间

先开辟20个int字节大小空间的运行结果(第二种情况)

        找到一块能容纳调整后的空间的地址,将数据移动到其中

关于参数size为0时的举例:

因为我们没有办法直接观察一块动态开辟的内存是否被释放,且这种size为0行为是未定义的,所以我们只能观察它的返回值

 

 关于参数ptr是NULL时的情况

此时realloc等同malloc

注意:动态内存管理的4个函数都包含在


常见的关于动态内存管理错误

1.对可能是NULL指针的引用

 

 2.对不是动态开辟的内存进行释放
// 2.对不是动态开辟的内存进行释放 #include #include int main() { 	int a = 0; 	int* p = &a; 	free(p); 	p = NULL; 	return 0; }

3.对动态开辟的内存进行越界访问
//3.对动态开辟的内存进行越界访问 #include int main() { 	int* p = (int*)malloc(sizeof(int)); 	p++; 	*p = 1; 	free(p); 	p = NULL; 	return 0; }

4.使用free释放动态开辟内存的一部分
//4.使用free释放动态开辟内存的一部分 #include int main() { 	int* p = (int*)malloc(4*sizeof(int));//动态开辟4个int大小的空间 	p++;//指向第二个元素 	free(p); 	p = NULL; 	return 0; }

5.忘记内存释放(忘记free),造成内存泄漏
//5.忘记内存释放(忘记free),造成内存泄漏 #include int main() { 	int* p = (int*)malloc(sizeof(int)); 	return 0; }

例题

1.

void GetMemory(char* p) { 	p = (char*)malloc(100); } void Test(void) { 	char* str = NULL; 	GetMemory(str); 	strcpy(str, "hello world"); 	printf(str); } int main() { 	Test(); 	return 0; }运行会咋样

p虽然在GetMemory函数中开辟了内存,但是在出函数时,该地址被销毁,所以str还是NULL指针,对NULL指针进行赋值是一个未定义行为。(传值调用而没有使用传址调用)

改正(二级指针):

void GetMemory(char** p)//使用二级指针接收 { 	*p = (char*)malloc(100); } void Test(void) { 	char* str = NULL; 	GetMemory(&str);//传入指针的地址 	strcpy(str, "hello world"); 	printf(str); } int main() { 	Test(); 	return 0; }

改正(将开辟的空间返回):

2.

char* GetMemory(void) { 	char p[] = "hello world"; 	return p; } void Test(void) { 	char* str = NULL; 	str = GetMemory(); 	printf(str); } int main() { 	Test(); 	return 0; }//运行结果?

GetMemory函数返回了一个地址,但是这个地址出了函数,权限已经收回给了操作系统,str接收的是一个野指针,并将它打印出来,这种行为是未定义的,可能造成错误。(说到底是栈空间返回会被销毁的问题)

我们知道只要是函数内的变量都是栈空间申请的空间,在出函数时,都会被回收,但是动态内存管理申请的空间,必须要手动释放,所以在函数中我们使用动态内存申请的地址是不会被收回的(堆区申请),所以我们尝试改正时,在函数内部使用动态内存申请,并返回。

改正:

char* GetMemory(void) { 	char* p = (char*)malloc(20); 	strcpy(p, "hello world!"); 	return p; } void Test(void) { 	char* str = NULL; 	str = GetMemory(); 	printf(str); } int main() { 	Test(); 	return 0; }

3.

//3 void GetMemory(char** p, int num) { 	*p = (char*)malloc(num); } void Test(void) { 	char* str = NULL; 	GetMemory(&str, 100); 	strcpy(str, "hello"); 	printf(str); } int main() { 	Test(); 	return 0; }//运行结果,以及问题

没释放空间,内存泄漏

改正:

void GetMemory(char** p, int num) { 	*p = (char*)malloc(num); } void Test(void) { 	char* str = NULL; 	GetMemory(&str, 100); 	strcpy(str, "hello"); 	printf(str);     free(ptr);     ptr=NULL; } int main() { 	Test(); 	return 0; }

使用了已经被释放的内存

相关内容

热门资讯

软件辅助挂!百乐科技辅助透视挂... 软件辅助挂!百乐科技辅助透视挂,来往苏州棋牌是有挂,果然有挂功能百乐科技透视方法中分为三种模型:百乐...
现场直击!德阳上下混辅助透视挂... 现场直击!德阳上下混辅助透视挂,中至上饶麻将真的是有挂,竟然了解有挂1、实时中至上饶麻将透视辅助更新...
2026版规律!木虱扑克辅助透... 2026版规律!木虱扑克辅助透视挂,九道麻将存在有挂,原来讲解有挂1、玩家可以在九道麻将线上大神俱乐...
今天上午!网趣沈阳麻将辅助透视... 您好,网趣沈阳麻将这款游戏可以开挂的,确实是有挂的,需要了解加去威信【136704302】很多玩家在...
一直以来!椰子大厅辅助透视挂,... 一直以来!椰子大厅辅助透视挂,家乡鹤游是真的有挂,好像有挂神器一、椰子大厅可以开透视的定义与意义1、...
黑科技辅助挂!亿彩辅助透视挂,... 黑科技辅助挂!亿彩辅助透视挂,中至乐平是有挂,原来有挂方法1、任何亿彩透视是真的假的的玩家都可以机会...
黑科技技巧!卡农大厅辅助透视挂... 黑科技技巧!卡农大厅辅助透视挂,55五子棋是有挂,一直有挂细节1、该软件可以轻松地帮助玩家将卡农大厅...
在玩家背景下!鑫微乐辅助透视挂... 在玩家背景下!鑫微乐辅助透视挂,乐驿茶坊真的有挂,都是有挂教程一、鑫微乐可以开透视的定义与意义1、鑫...
现就发布提示!扬州麻将辅助透视... 现就发布提示!扬州麻将辅助透视挂,聚友联盟是有挂,一直有挂技巧1、扬州麻将透视辅助软件激活码多个强度...
据相关数据显示!摇钱树辅助透视... 据相关数据显示!摇钱树辅助透视挂,沅江麻将真的是有挂,确实发现有挂1、摇钱树公共底牌简单,摇钱树透视...