c语言-动态内存管理

这篇具有很好参考价值的文章主要介绍了c语言-动态内存管理。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


一、为什么会有动态内存管理

1.我们一般的开辟空间方式:

int a = 0;//申请4个字节空间
int arr[10] = { 0 };//申请40个字节空间

2.这样开辟空间的特点

(1)申请的空间大小是固定的
(2)像数组那样一开始就要确定大小,一旦确定大小就不能改变了

3.动态内存

对于程序来说上述的内存申请是不能满足 因此为了能够对内存进行调整,C语言引入了动态内存开辟,让程序员自己可以申请和释放空间,就比较灵活了。

二、申请内存函数

以下动态申请的内存都是向堆区申请的,
并且都申请内存的函数和释放函数都包含在头文件 :#include<stdlib.h>

1、malloc

(1)返回类型和参数:

void* malloc(size_t size);//返回类型为 void*  ,参数为正整数单位是字节

因为返回类型为 void*,所以在malloc函数是不知道我们想要申请什么类型的空间,面对这种情况我们要将返回的类型进行强制类型转换,这样就能返回我们需要的类型了,参数是申请的大小

(2)作用:

向内存申请一块连续的空间,并返回指向这块空间的的指针

(3)注意:

a.在申请完之后我们还要判断是否成功申请
当申请失败时会返回NULL
当申请成功后就返回指向这块空间的的指针,这样可以正常使用这块空间了
b.如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器

(4)使用:

int main() {
	int* p = (int*)malloc(sizeof(int) * 10);//向内存申请40个字节空间
	if (p == NULL)//判断是否申请成功
		return 1;//失败直接放回
	for (int i = 0; i < 10; i++)//成功就正常使用
		*(p + i) = i;
	for (int i = 0; i < 10; i++)//打印
		printf("%d ", *(p + i));
	return 0;
}

运行结果:
c语言-动态内存管理,c语言主要知识点,c语言,算法,服务器,开发语言,笔记,经验分享

2、free

(1)返回类型和参数:

void free( void* p)//参数为向动态内存申请的空间的指针,返回类型为空

(2)作用:

专门进行对动态内存的释放和回收

(3)注意:

a.当释放的内存不是动态开辟的,这是free未定义的
b.当p是NULL时,free函数什么事都不发生
c.当我们将p指向的空间释放后,要将p置空,不然p就成野指针了

(4)使用

在我们上一个代码中,并未对malloc函数开辟的空间进行释放,其实这是不对的,这会造成内存泄漏。 那么我们就来用一下free函数吧

int main() {
	int* p = (int*)malloc(sizeof(int) * 10);//向内存申请40个字节空间
	if (p == NULL)//判断是否申请成功
		return 1;//失败直接放回
	for (int i = 0; i < 10; i++)//成功就正常使用
		*(p + i) = i;
	for (int i = 0; i < 10; i++)//打印
		printf("%d ", *(p + i));
	free(p);//释放
	p = NULL;//及时置空,防止出现野指针
	return 0;
}

这样代码才算完整。

3、calloc

(1)返回类型和参数:

void *calloc(size_t n,size_t  size);

返回类型为 void* ,所以和malloc一样想要什么类型的空间就进行强制转换类型即可,第一个参数为申请的个数,第二个参数为申请的类型的空间大小(单位为字节)

(2)作用:

申请一块连续的空间,并将空间的内容全部初始化为0,然后返回指向这块空间的指针

(3)注意:

a.在申请完之后我们还要判断是否成功申请
当申请失败时会返回NULL
当申请成功后就返回指向这块空间的的指针,这样可以正常使用这块空间了
b.如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器
c.与malloc的区别就是calloc会将申请的空间初始化,这样使用时更方便

(4)使用:

int main() {
	int* p = (int*)calloc(10,sizeof(int));//申请
	if (p == NULL) {//判断
		printf("NULL");
		return 1;
	}
	//使用
	for (int i = 0; i < 10; i++)
		*(p + i) = i;
	for (int i = 0; i < 10; i++)
		printf("%d ", *(p + i));
	free(p);//同样的释放空间
	p = NULL;//置空
	return 0;
}

运行结果:
c语言-动态内存管理,c语言主要知识点,c语言,算法,服务器,开发语言,笔记,经验分享

4、realloc

(1)返回类型和参数:

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

返回类型为 void* 所以和calloc一样想要什么类型就强制类型转换,第一个参数p为指向想要的改变的空间的 指针,第二参数为改变的大小(单位为字节)

(2)作用:

在原来的动态内存的空间上增大或者缩小,并返回改变后指向新的空间的指针

(3)注意:

a.开辟失败返回空指针
b.开辟的新的空间时有两种开辟的方式,第一种是我们原本的空间后面有足够的空间,那样直接在原来的空间后面扩容,第二种是我们原本的空间后面没有足够的空间,那样的话,系统就会在内存上找一块适合的空间重新开辟,并将原来空间的内容复杂过去,再将原来的空间销毁。
由于开辟的情况有两种,所以我们使用时要注意开辟空间失败这种情况
如:我们使用了第二种情况开辟空间、并直接用原来的指针去接收指向开辟的空间的指针,如果开辟失败的话返回NULL,这导致原来的数据会丢失。
所以为了解决这个隐患,我们可以重新定义一个指针来接收,判断不为空再将该指针赋给原来的指针。

图:
c语言-动态内存管理,c语言主要知识点,c语言,算法,服务器,开发语言,笔记,经验分享
(4)使用:

int main() {
	//先用动态内存开辟一个空间
	int* p = (int*)calloc(10, sizeof(int));
	if (p == NULL)
		return 1;
	for (int i = 0; i < 10; i++)
		*(p + i) = i;
	for (int i = 0; i < 10; i++)
		printf("%d ", *(p + i));
	printf("\n");
	//使用realloc增加空间
	int* pp = (int*)realloc(p, sizeof(int) * 15);//再申请一个指针变量来接收
	if (pp == NULL)
		return 1;
	else
		p = pp;//不为空再赋给原来的指针
	for (int i = 10; i < 15; i++)
		*(p + i) = i;
	for (int i = 10; i < 15; i++)
		printf("%d ", *(p + i));
	free(p);//最后不要忘记了释放并置空
	p = NULL;
	return 0;
}

运行结果:
c语言-动态内存管理,c语言主要知识点,c语言,算法,服务器,开发语言,笔记,经验分享

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

1、对NULL指针的解引用操作

void test()
 {
 int *p = (int *)malloc(INT_MAX/4);//开辟空间
 //这里我们不知道是否开辟成功
 *p = 20;//如果p的值是NULL,就会有问题
 free(p);
 p=NULL;
 }

这里的问题是不知道p为不为NULL
改:

void test()
 {
 int *p = (int *)malloc(INT_MAX/4);//开辟空间
 if(p!=NULL)
 *p = 20;
 free(p);
 p=NULL;
 }

2 、对动态开辟空间的越界访问

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

改:

void test()
 {
 int i = 0;
 int *p = (int *)malloc(10*sizeof(int));
 if(NULL == p)
 {
 exit(EXIT_FAILURE);
 }
 for(i=0; i<10; i++)//当我们改成<10之后i就不会到10,也就不会越界了
 {
 *(p+i) = i;
 }
 free(p);
 p=NULL;
 }

3 、对非动态开辟内存使用free释放

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

这种行为在free函数中未定义,最好不要使用
4 、使用free释放⼀块动态开辟内存的⼀部分

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

这种行为会导致内存泄漏
5 、对同⼀块动态内存多次释放

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

当出现这种情况程序会崩掉
6、 动态开辟内存忘记释放(内存泄漏)

void test()
 {
 int *p = (int *)malloc(100);
 if(NULL != p)
 {
 *p = 20;
 }
 }
int main()
 {
 test();
 while(1);
 }

这种未对申请的空间释放,会导致内存泄漏

四、练习

1、请问运行Test 函数会有什么样的结果?

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

运行结果:
c语言-动态内存管理,c语言主要知识点,c语言,算法,服务器,开发语言,笔记,经验分享
什么都没有输出,这是为什么呢?
这是因为调用GetMemory函数时使用的是传值调用,p虽然申请到了空间,但是并没有改变str的值,所以str=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;
}

运行结果:
c语言-动态内存管理,c语言主要知识点,c语言,算法,服务器,开发语言,笔记,经验分享
成功打印
2、请问运行Test 函数会有什么样的结果?

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

运行结果:
c语言-动态内存管理,c语言主要知识点,c语言,算法,服务器,开发语言,笔记,经验分享
出现了随机值,这是为什么呢?
因为p是在栈区创建,当函数结束后该p指向的空间也会销毁然后返还给内存,此时将p传给str之后,str就会变成野指针,它指向的空间打印出来的就是随机值了
改:我们可以用动态内存
如:

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;
}

运行结果:
c语言-动态内存管理,c语言主要知识点,c语言,算法,服务器,开发语言,笔记,经验分享
3、请问运行Test 函数会有什么样的结果?

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

运行结果:
c语言-动态内存管理,c语言主要知识点,c语言,算法,服务器,开发语言,笔记,经验分享
输出正确。但是这里的问题时没有释放动态内存
改:

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(str);
	str = NULL;
}
int main() {
	Test();
	return 0;
}

4、请问运行Test 函数会有什么样的结果?

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

运行结果:
c语言-动态内存管理,c语言主要知识点,c语言,算法,服务器,开发语言,笔记,经验分享
虽然结果正确,但是其实是有问题的
1.因为str存储的地址不会改变,应该手动置空,但是它释放空间后没有置空
2.使用释放的空间(这块空间已经还给系统了)
3.为什么还能打印world呢?那是因为该块空间没有被覆盖,world还在那里
我们可以试试再他前面再申请一次动态内存看看

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

运行结果:
c语言-动态内存管理,c语言主要知识点,c语言,算法,服务器,开发语言,笔记,经验分享
world被覆盖了,就打印不出了
改:我们可以释放后置空,就不会出现这种情况了

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

以上就是我的分享了,如果有什么错误,欢迎在评论区留言。
最后,谢谢大家的观看!文章来源地址https://www.toymoban.com/news/detail-762913.html

到了这里,关于c语言-动态内存管理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

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

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

相关文章

  • 计算机语言知识点梳理

    python是一门强类型、动态型、解释型语言语言 JavaScript是一门弱类型、动态性、解释型语言 typescript是一门强类型、静态型、编译型语言 Java是一门强类型、静态型、编译型语言 变量大小写区分 html css sql java javascript python typescript c 弱类型语言和强类型语言 强类型语言也称为强

    2024年02月09日
    浏览(48)
  • 【C语言】指针知识点笔记(2)

    目录 一、野指针 二、assert断言 三、指针的使用和传址调用 四、数组名的理解 五、使用指针访问数组

    2024年01月18日
    浏览(43)
  • 单片机----汇编语言入门知识点

    目录 汇编语句的格式 汇编语句的两个基本语句 子程序的调用 查表程序设计 1.x和y均为单字节数的查表程序设计 2.x为单字节数y为双字节数的查表程序设计 3.x和y均为双字节数的查表程序设计 分支转移程序设计 1.单分支选择结构 2.多分支选择结构 循环程序设计 (1) 计数循环控

    2024年04月28日
    浏览(48)
  • 【详解】C语言冷门知识点之--位段

    下面是维基百科对位段的解释:   位段(或称“位域”,Bit field)为一种数据结构,可以把数据以位的形式紧凑的储存,并允许程序员对此结构的位进行操作。这种数据结构的好处: 可以使数据单元节省储存空间,当程序需要成千上万个数据单元时,这种方法就显得尤为重

    2024年02月16日
    浏览(41)
  • 带宽管理:知识点概述及防火墙带宽管理实验

    目录 一、带宽管理技术介绍 1.1 基本概念: 1.2 带宽管理原理介绍: 1.3 接口带宽原理: 1.4 带宽策略原理: 1.5 带宽通道原理 1.6 带宽复用 二、带宽管理实验 1.1 基本概念: 带宽管理对通过自身的流量进行管理和控制:提供带宽保证。 提供带宽限制。 提供连接数限制功能。

    2024年02月15日
    浏览(43)
  • C语言:数组、字符串知识点整理:

    补充 :数组长度= sizeof(arr)/sizeof(arr[0]) 注意: !!! 不适用于当arr 充当形参时(函数传参) !!! 因为函数 传递 的是 int arr[0] 的 地址(指针) ,而 不是数组内容 , 若在此情况计算整形数组长度         在X86系统下,指针大小为 4 ,所以计算结果为4/4=1;       

    2024年03月10日
    浏览(60)
  • C语言指针入门学习、知识点梳理(四)

    本篇内容:指针星号的三种含义以及指针在函数调用时,与实参、形参的变量传递关系,如何通过被调函数修改主调函数普通变量的值。 传送门: C语言指针入门学习、概念梳理(一) C语言指针入门学习、概念梳理(二) C语言指针入门学习、知识点梳理(三) C语言指针入门

    2024年02月09日
    浏览(44)
  • C++入门知识点——解决C语言不足

    😶‍🌫️😶‍🌫️😶‍🌫️😶‍🌫️Take your time ! 😶‍🌫️😶‍🌫️😶‍🌫️😶‍🌫️ 💥个人主页:🔥🔥🔥大魔王🔥🔥🔥 💥所属专栏:🔥魔王的修炼之路–C++🔥 如果你觉得这篇文章对你有帮助,请在文章结尾处留下你的 点赞 👍和 关注 💖,支持一下博主

    2024年02月12日
    浏览(47)
  • 【C语言】动态内存管理基础知识——动态通讯录,如何实现通讯录容量的动态化

    动态内存管理的函数有:malloc,calloc,ralloc,free,本文讲解动态内存函数和使用,如何进行动态内存管理,实现通讯录联系人容量的动态化,对常见动态内存错误进行总结。                           ✨  猪巴戒 :个人主页✨                 所属专栏 :《C语言进阶》

    2024年02月04日
    浏览(68)
  • C语言零碎知识点之字符串数组

    在C语言中字符串数组有两种表示方法: 第一种, 二维字符数组 可以利用二维字符数组来表示字符串,格式是: char strs[row][col]; 。例如: 其中的 row 表示二维数组中的行,表示有几个字符串;而 col 表示二维数组中的列,表示能存放字符串的最大长度。 第二种, 字符指针数

    2024年01月18日
    浏览(49)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包