【C语言】动态内存管理(C语言的难点与精华,数据结构的前置知识,你真的掌握了吗?)

这篇具有很好参考价值的文章主要介绍了【C语言】动态内存管理(C语言的难点与精华,数据结构的前置知识,你真的掌握了吗?)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

引言

学习专栏

《零基础学C语言》
《数据结构世界》

俗话说的好,要想学好数据结构(数据结构世界,对数据结构感兴趣的小伙伴可以移步),就必须学好以下三方面知识:

  1. 指针
    不允许你还不了解指针的那些事(一)(内存和地址+指针变量+指针运算+野指针+传址调用)
    不允许你还不了解指针的那些事(二)(数组传参的本质+冒泡排序+数组指针+指针数组)
  2. 结构体
    自定义类型:结构体(你真的掌握了内存对齐,位段吗?)
  3. 动态内存管理

前两方面的知识在往期已经详细讲解,今天我们就来学习最后一方面的知识——动态内存管理

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

我们已经掌握的内存开辟方式有:

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

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

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

所以,为了能在程序运行中,根据需求灵活地调整空间大小,C语言引入了动态内存管理。

二、动态内存分配的相关函数

2.1 malloc

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

void* malloc (size_t size);

这个函数可以开辟一块连续的内存空间,并返回指向这块空间的指针

  • 如果开辟成功,则返回指向开辟好空间的指针。
  • 如果开辟失败,则返回NULL 指针,因此malloc的返回值一定要做检查。
  • 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
int main()
{
	//动态开辟10个整型空间
	int* a = (int*)malloc(10 * sizeof(int));
	if (a == NULL)//开辟不成功,返回NULL
	{
		perror("malloc fail");
		return 1;
	}
	//开辟成功,使用该空间
	//...
	return 0;
}

注意:malloc(0)——开辟0个字节空间,是标准未定义行为,具体取决于编译器

2.2 free

C语言提供了另外一个函数free,可以动态内存的释放和回收:

void free (void* ptr);

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

  • 如果参数 ptr 是指向动态内存空间的指针,则释放该空间
  • 如果参数 ptr 是NULL指针,则不作任何处理。
int main()
{
	//动态开辟10个整型空间
	int* a = (int*)malloc(10 * sizeof(int));
	if (a == NULL)//开辟不成功,返回NULL
	{
		perror("malloc fail");
		return 1;
	}
	//开辟成功,使用该空间
	//使用完毕,释放该空间
	free(a);
	a = NULL;
	return 0;
}

注意:如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的

2.3 calloc

C语言还提供了一个函数叫 calloc , calloc 函数也用来动态内存分配。

void* calloc (size_t num, size_t size);

其实,它与malloc很相似,区别在于calloc会将开辟的连续空间,每个字节都初始化为0

int main()
{
	//动态开辟10个整型空间并初始化为0
	int* a = (int*)calloc(10, sizeof(int));
	if (a == NULL)//开辟不成功,返回NULL
	{
		perror("calloc fail");
		return 1;
	}
	//开辟成功,使用该空间
	//使用完毕,释放该空间
	free(a);
	a = NULL;
	return 0;
}

2.4 realloc

realloc函数的出现让动态内存管理更加灵活。
为了合理的运用内存,所以在开辟空间后觉得太大或太小,就可以使用realloc进行调整。

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

realloc调整成功,会返回调整后指向这块空间的指针

  • ptr 是调整前空间的指针
  • size 是调整后的新大小
  • 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间。

realloc在调整内存空间的是存在两种情况:

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

这个时候,realloc会直接在原有空间后面直接追加开辟。
【C语言】动态内存管理(C语言的难点与精华,数据结构的前置知识,你真的掌握了吗?),零基础学C语言,c语言,数据结构,开发语言

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

这个时候,realloc就会再找一块足够大空间,一次性开辟完,再将原数据拷贝过去。
【C语言】动态内存管理(C语言的难点与精华,数据结构的前置知识,你真的掌握了吗?),零基础学C语言,c语言,数据结构,开发语言

int main()
{
	//动态开辟10个整型空间
	int* a = (int*)malloc(10 * sizeof(int));
	if (a == NULL)//开辟不成功,返回NULL
	{
		perror("malloc fail");
		return 1;
	}
	//调整空间,扩大为20个整型空间
	int* tmp = (int*)realloc(a, 20 * sizeof(int));
	if (tmp == NULL)//开辟不成功,返回NULL
	{
		perror("realloc fail");
		return 1;
	}
	//调整成功,将空间地址给a
	a = tmp;
	//使用空间...
	free(a);
	a = NULL;
	return 0;
}

注意

  • 用临时变量tmp来接受realloc调整后的空间地址
  • 因为如果用a接收,万一调整失败返回NULL,还会丢掉原本空间的地址

三、常见的动态内存的错误

3.1 对NULL指针的解引用

void test()
{
	 int *p = (int *)malloc(INT_MAX/4);
	 *p = 20;//如果p的值是NULL,就会有问题
	 free(p);
}

3.2 对动态内存越界访问

void test()
{
	int i = 0;
	int *p = (int *)malloc(10*sizeof(int));
	if(NULL == p)
	{
		exit(-1);//终止程序
	}
	for(i=0; i<=10; i++)
	{
		*(p+i) = i;//当i是10的时候越界访问
	}
	free(p);
}

3.3 对非动态内存释放

void test()
{
	int a = 10;
	int *p = &a;
	free(p);//ok?
}

3.4 对动态内存部分释放

void test()
{
	int *p = (int *)malloc(100);
	p++;
	free(p);//p不再指向动态内存的起始位置
}

3.5 对动态内存多次释放

void test()
{
	int *p = (int *)malloc(100);
	free(p);
	free(p);//重复释放
}

3.6 未对动态内存释放(内存泄漏)

因为程序终止会回收所有空间,所以这里用死循环来模拟程序运行中不释放动态内存,会导致内存泄漏。

int main()
{
	int* a = (int*)malloc(10 * sizeof(int));
	while(1);
	return 0;
}

总结动态开辟的内存一定要释放,并且要正确释放

四、动态内存经典笔试题分析

请先自行思考哦,不要立马看解析~

请问以下题目运行Test 函数会有什么样的结果?

4.1 题目一

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

结果:

  • 程序崩溃,内存泄漏

解析:

  • GetMemory函数中,动态开辟了100个字节空间。但是p是一个形参,形参是实参的一份临时拷贝,对形参的影响无法改变实参,所以str还是NULL。
  • 在运行strcpy函数时,因为要拷贝的目的地是NULL,所以程序崩溃。
  • 同时,动态开辟的空间未释放,还导致了内存泄漏。

4.2 题目二

char *GetMemory(void)
{
	char p[] = "hello world";
	return p;
}
void Test(void)
{
	char *str = NULL;
	str = GetMemory();
	printf(str);
}

结果:

  • 有可能打印出乱码

解析:

  • GetMemory函数中,开辟了p数组。p是局部变量,出了函数作用域就会销毁(空间被回收)。
  • 所以,str接收p的地址时,此时已经指向一块被回收的空间。
  • 打印该空间的内容,是否为乱码,取决于该空间的内容是否被其余数据覆盖

4.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);
}

结果:

  • 内存泄漏,有可能程序崩溃

解析:

  • 这次进行传址调用,二级指针解引用,确实能改变外部str
  • 但是接收到空间地址后,未进行检查,如果开辟失败,为NULL,则strcpy时依旧会程序崩溃
  • 同时未对动态内存释放,造成内存泄漏

4.4 题目四

void Test(void)
{
	char *str = (char *) malloc(100);
	strcpy(str, "hello");
	free(str);
	if(str != NULL)
	{
		strcpy(str, "world");
		printf(str);
	}
}

结果:

  • 后果未知

解析:

  • 假设能正常开辟动态内存
  • strcpy拷贝完hello,并free进行释放
  • 因为free不会自动将str置为NULL,所以以下判断不起作用,“拦不住”str这个野指针
  • 最后strcpy将world拷贝到已被释放的空间,再进行打印(对已被释放的动态内存进行操作,后果未知,十分危险,很严重!)

五、柔性数组

也许你从来没有听说过柔性数组(flexible array)这个概念,但是它确实是存在的。
C99 中,结构中的最后⼀个元素允许是未知大小的数组,这就叫做『柔性数组』成员。

typedef struct st_type
{
	int i;
	int a[0];//柔性数组成员
}type_a;

有些编译器会报错无法编译可以改成:

typedef struct st_type
{
	int i;
	int a[];//柔性数组成员
}type_a;

5.1 柔性数组的特点

  • 结构中的柔性数组成员前面必须至少一个其他成员
  • sizeof 返回的这种结构大小不包括柔性数组的内存
  • 包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
typedef struct st_type
{
	int i;
	int a[0];//柔性数组成员
}type_a;
int main()
{
	printf("%d\n", sizeof(type_a));//输出的是4
	return 0;
}

5.2 柔性数组的使用

int main()
{
	int i = 0;
	type_a *p = (type_a*)malloc(sizeof(type_a)+100*sizeof(int));
	//使用空间
	p->i = 100;
	for(i=0; i<100; i++)
	{
		p->a[i] = i;
	}
	free(p);
	return 0;
}

这样柔性数组成员a,相当于获得了100个整型元素的连续空间。

六、C/C++中程序内存区域划分

【C语言】动态内存管理(C语言的难点与精华,数据结构的前置知识,你真的掌握了吗?),零基础学C语言,c语言,数据结构,开发语言

C/C++程序内存分配的几个区域:

  1. 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。
  2. 堆区(heap):一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。分配方式类似于链表。
  3. 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。
  4. 代码段:存放函数体(类成员函数和全局函数)的二进制代码。

看到这里了还不给博主扣个:
⛳️ 点赞☀️收藏 ⭐️ 关注!
💛 💙 💜 ❤️ 💚💓 💗 💕 💞 💘 💖
拜托拜托这个真的很重要!
你们的点赞就是博主更新最大的动力!
有问题可以评论或者私信呢秒回哦。文章来源地址https://www.toymoban.com/news/detail-753992.html

到了这里,关于【C语言】动态内存管理(C语言的难点与精华,数据结构的前置知识,你真的掌握了吗?)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 「探索C语言内存:动态内存管理解析」

    🌠先赞后看,不足指正!🌠 🎈这将对我有很大的帮助!🎈 📝所属专栏:C语言知识 📝阿哇旭的主页:Awas-Home page 目录   引言 1. 静态内存 2. 动态内存 2.1 动态内存开辟函数 2.1.1 malloc函数 2.1.2 calloc函数 2.1.3 realloc函数 2.2 动态内存释放函数 2.2.1 free函数 3. 动态内存的常见

    2024年04月28日
    浏览(26)
  • C语言:动态内存管理

    先点赞再观看哦! 学习数据结构之前,一定要对指针、结构体、动态内存管理进行深入学习! 小伙伴们可以看看博主之前的文章! 今天重点介绍动态内存开辟!十分重要哈! 我们已知的内存开辟方式有什么呢?? 但是上述开辟的空间有三个特点: 1、空间开辟的大小是固定

    2024年01月22日
    浏览(29)
  • <C语言> 动态内存管理

    为什么存在动态内存分配? 上述的开辟空间的方式有两个特点: 空间开辟大小是固定的。 数组在声明的时候,必须指定数组的长度,它所需要的内存在编译时分配。 但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,那数组

    2024年02月15日
    浏览(29)
  • 动态内存管理-c语言

    目录 1.为什么要有动态内存分配 2.malloc函数和free函数 malloc 函数原型 栗子 free 函数原型 栗子 3.calloc和***realloc*** 3.1calloc函数 原型如下: 栗子 3.2***recalloc*** 第一种情况 第二种情况 第三种情况 recalloc模拟实现calloc函数 4.六大常⻅的动态内存的错误 4.1对NULL指针的解引⽤操作

    2024年03月22日
    浏览(28)
  • 【C语言】动态内存管理

    大家好,我是苏貝,本篇博客带大家了解动态内存管理,如果你觉得我写的还不错的话,可以给我一个赞👍吗,感谢❤️ 我们已经掌握的内存开辟方式有: int val = 20; 在栈空间上开辟四个字节 char arr[10] = {0}; 在栈空间上开辟10个字节的连续空间 但是上述的开辟空间的方式有

    2024年01月16日
    浏览(27)
  • c语言-动态内存管理

    1.我们一般的开辟空间方式: 2.这样开辟空间的特点 (1)申请的空间大小是固定的 (2)像数组那样一开始就要确定大小,一旦确定大小就不能改变了 3.动态内存 对于程序来说上述的内存申请是不能满足 因此为了能够对内存进行调整,C语言引入了动态内存开辟,让程序员自

    2024年02月04日
    浏览(26)
  • 动态内存管理 --- C语言

    目录 1.为什么存在动态内存管理 2.动态内存函数的介绍 2.1 malloc 与 free  2.2 calloc 2.3 realloc 3.常见的动态内存错误 4.几个经典笔试题 6.柔性数组 我们已经掌握的内存开辟方式有: 但是上述的开辟空间的方式有两个特点: 空间开辟的 大小是固定的 。 数组在声明的时候, 必须指

    2024年02月11日
    浏览(74)
  • C语言->动态内存管理

    文章目录  ✅作者简介:大家好,我是橘橙黄又青,一个想要与大家共同进步的男人😉😉 🍎个人主页:橘橙黄又青_C语言,函数,指针-CSDN博客 目的:学习malloc,free,calloc,realloc函数的使用。 内存函数在#includestdio.h头文件里面。 我们已经掌握的内存开辟⽅式有: 但是上述的

    2024年02月04日
    浏览(33)
  • 动态内存管理(C语言)

    我们已经掌握的内存开辟方式有 int val = 20;//在栈空间上开辟四个字节 char arr[10] = {0};//在栈空间上开辟10个字节的连续空间 但上述开辟内存的方式有两个特点 空间开辟大小是固定的 数组在声明时必须指定数组的长度,他所需要的内存在编译时分配 但是对于空间的需求,不仅

    2024年02月16日
    浏览(32)
  • 【C语言:动态内存管理】

    文章的标题是动态内存管理,那什么是动态内存管理?为什么有动态内存管理呢? 回顾一下以前学的知识,我们已经掌握的开辟内存的方式有以下几种: 上述开辟内存的方式有几个弊端: 开辟空间的大小是固定的 数组在声明的时候,必须指定数组的长度,数组空间⼀旦确定

    2024年02月03日
    浏览(28)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包