C语言:动态内存管理

本文的参考资料均来自cplusplus网站:

cplusplus.com - The C++ Resources Networkhttp://www.cplusplus.com/

一、动态内存分配基本了解

在介绍动态内存函数之前,我们要先对动态内存分配有一个基本的了解,所以就引出了一个问题:为什么存在动态内存分配呢?

实际上,在我们写的程序中,比如数组等都会在一个指定的内存中合适位置开辟一块空间,类似的都会在其栈上开辟空间,这样的开辟空间方式有两个特点:一是空间开辟大小是固定的;二是数组在声明的时候,必须指定数组的长度,它所需要的内存在编译是分配。但是有时候这样就会出现一种情况,那就是我们需要的空间大小要在程序运行的时候才能知道,也就是说在写代码的时候,我们是不知道自己要开辟多大空间的,那么数组的编译时开辟空间的方式就不能满足了。所以这时候,就会有动态内存的出现,而动态内存不同的是其是在堆上开辟的。

二、动态内存函数

了解完动态内存分配的原因后,接下来就来介绍4个重要的动态内存函数。

1. malloc

有关malloc的资料:

关于这个函数有几点需要注意:

1. 这个函数向内存申请的是一块连续可用的空间

2. 因为如果开辟失败(比如开辟一块很大的空间),会返回NULL,所以在开辟动态内存后一定要先判断返回的指针是否等于NULL,一定要先做检查!!!

3. 如果参数size为0,malloc的行为是标准未定义的,取决于编译器

2. calloc

有关calloc的资料:

calloc函数和malloc函数都是用来开辟动态内存空间的,注意的点基本是一样的,但他们的区别只在于calloc会在返回地址之前把申请的空间的每个字节都初始化为全0。实际使用应该根据需求来定,calloc赋初值为0,malloc不赋初值,但可以在下面代码中自己赋想要的值 。

3. realloc 

有关realloc的资料:

realloc函数可以让动态内存管理更加灵活,就比如:有时候会发现自己之前申请的空间太小或者太大了,那为了合理地使用内存,就一定会对内存的大小做灵活处理。 

realloc函数相对于前面两个函数来说会比较复杂一点,这里再做一下整理:

1. ptr是要调整的内存地址

2. size是调整之后的新大小(以字节为单位)

3. 调整成功返回值为调整之后的内存起始位置;调整失败则不会修改之前开辟的空间(ptr指向的块没有被修改),返回NULL

4. 这个函数在调整原内存空间大小的基础上,还可能会将原来内存中的数据移动到新的空间,这时候就有两种情况,接下来对这两种调整内存空间存在的情况进行讨论:情况一是原空间之后有足够大的空间可以来扩大,那么要扩展的内存就直接追加到原有的内存后面的空间就好,就不会将原有内存中的数据移动到新的空间,也就是说原来空间的数据是不发生变化的;而情况二是原有空间之后没有足够大的空间,那么其扩展方法则是在堆空间上另找一个合适大小的连续空间来使用,这样函数返回的是一个新的内存地址,然后会将原来的那块内存空间释放掉。

5. 如果参数ptr写为NULL,那么realloc函数就可以从调整内存空间变为开辟内存空间,其作用与malloc函数一样

4. free

有关free的资料:

free函数是用来释放动态开辟的内存,但要注意的两点是:

1. 如果参数ptr指向的空间不是动态开辟的(也就是不是在堆上的),那free函数行为是未定义的

2. 如果参数ptr是NULL指针,则函数什么事都不做

那这时候就有一个问题了,为什么free函数只能是针对动态开辟的内存呢?

因为除了动态内存之外的其他形式开辟空间,基本上都是在栈上开辟的(还有一些全局变量是在全局区或者静态区开辟),随着生命周期的结束,他们也会被自动销毁的,所以是不用我们再来释放的;而动态内存是在堆上开辟空间的,在堆上开辟的空间是不会被自动销毁的,因此要用free函数来将这块空间释放掉,如果不释放,则可能会导致内存泄露,这是后面的问题了 ,还有在动态内存释放后我们还要将指向这个空间的指针赋成空指针NULL,因为释放后的指针就变成野指针了,非常危险,所以一般都会记住两点:一是指针不知道赋什么值,就给NULL;二是指针使用完后,就赋值NULL。要养成这样编程习惯,一般就不会有大问题。

三、C/C++程序的内存开辟(简单)

这是一张C/C++程序内存中比较完整的分配区域图,其中有几个点要详细说明:

1. 栈区:在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时,这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存有限。栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。

2. 堆区:一般由程序员分配释放,若程序不释放,程序结束时可能由OS(操作系统)回收。分配方式类似于链表。

3. 数据段(全局区/静态区):存放全局变量、静态数据。程序结束后由系统释放。

4. 代码段:存放函数体(类成员函数和全局函数)的二进制代码。

通过这个程序内存区域划分图就能进一步解释之前说到的static关键字修饰局部变量了:那就是原来普通的局部变量是在栈区分配空间的,栈区的特点是在上面创建的变量出了作用域就销毁。但是被static修饰的变量存放在数据段(全局区/静态区),数据段的特点是在上面创建的变量,直到程序结束才销毁,所以生命周期变长。

四、柔性数组 

柔性数组是什么?

柔性数组其实是一个比较新的概念,在C99中,结构中的最后一个元素允许是未知大小的数组,这就叫做柔性数组成员。

就比如这样:

typedef struct Code
{
	int i;
	int a[0];
}Code;

又或者这样:

typedef struct Code
{
	int i;
	int a[];
}Code;

两种写法都是正确的,但要看编译器,不同编译器可能不一样。

知道了什么是柔性数组,接下来就是看柔性数组的特点:

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

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

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

下面用代码展示来说一下柔性数组的使用:

typedef struct st_type
{
	int i;
	int a[0];
}type_a;

int main()
{
	type_a* ps = (type_a*)malloc(sizeof(type_a) + 40);
	if (ps == NULL)
	{
		return 0;
	}
	ps->i = 100;
	//增容
	type_a* ptr = (type_a*)realloc(ps, sizeof(type_a) + 80);
	if (ptr == NULL)
	{
		return 0;
	}
	else
	{
		ps = ptr;
	}
	//释放
	free(ps);
	ps = NULL;
	return 0;
}

此代码充分展示了柔性数组的柔性,在结构中,我们不确定当中的数组应该开辟多大的一块空间,这时候就可以用malloc和realloc函数来对数组进行增容等。

但是这时候可能会有人说对于这道题开辟两次动态内存来对其中一个指针进行增容,不也一样可以实现相同的功能吗?为什么非要使用柔性数组?就比如下面的代码:

typedef struct st_type
{
	int i;
	int* a;
}type_a;

int main()
{
	type_a* ps = (type_a*)malloc(sizeof(type_a));
	if (ps == NULL)
	{
		return 0;
	}
	ps->a = (int*)malloc(40);
	ps->i = 100;
	//增容
	type_a* ptr = (type_a*)realloc(ps->a, 80);
	if (ptr == NULL)
	{
		return 0;
	}
	else
	{
		ps->a = ptr;
	}
	//释放
	free(ps->a);
	ps->a = NULL;
	free(ps);
	ps = NULL;
	return 0;
}

虽然这样的代码也可以实现与柔性数组相同的功能,但是对于开辟两次动态内存来说,使用柔性数组的好处/优势在于:

1. 方便内存释放:第二种方法开辟和释放的次数都多,释放的先后顺序也有要求,非常容易出错

2. 有利于访问速度:连续内存有益于提高访问速度,也有益于减少内存碎片。第二种方法,两次开辟的内存都不在同一块空间,很容易导致内存碎片化,影响性能。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蔡欣致

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值