C语言堆区(动态内存开辟)

1.为什么存在动态内存开辟

<1>在技术方面,普通的空间申请,都是在全局或者栈区,全局一般不太建议大量使用,而栈空间有限,那么如果一个应 用需要大量的内存空间的时候,需要通过申请堆空间来支持基本业务。

<2>在应用方面,程序员很难一次预估好自己总共需要花费多大的空间。想想之前我们定义的所有数组,因为其语法约束,我们必须得明确"指出"空间大小.但是如果用动态内存申请(malloc)因为malloc是函数,而函数就可以传参,也就意味着,我们可以通过具体的情况,对需要的内存大小进行动态计算,进而在传参申请,提供了很大的灵活性 。

二.动态内存函数的介绍

1.malloc函数

void* malloc (size_t size);

这个函数向堆区域内存申请一块连续可用的空间,并返回指向这块空间的指针。如果开辟成功,则会返回一个指向开辟好空间的指针。若果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。

<2>返回值的类型的void*,所以malloc的函数并不知道开辟空间的类型,具体在使用的时候由使用者来决定。

<3>若果参数size为0,malloc的行为是标准未定义的,取决于编译器。

2.free函数

void free (void* ptr);

是用来释放动态开辟的内存,如果参数ptr指向的空间不是动态开辟的,那么free函数的行为是未定义的,如果参数ptr是NULL空指针,则函数什么事都不做。

3.calloc函数

void* calloc (size_t num, size_t size);

也用来动态内存分配,函数的功能是为num个大小为size的元素开辟一块空间,并且把空间的每个字节初始化为0。与函数malloc的区别只在于calloc会在返回地址之前把申请的空间的每个字节初始化为0。

4.realloc函数

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

的出现让动态内存管理更加灵活。有时候我们无法对申请的空间进行准确的需求判断,realloc函数可以做到对动态开辟内存大小的调整。其中ptr是调整的内存地址,size调整之后新大小,返回值为调整之后的内存起始位置。这个函数调整原内存大小空间的基础上,还会将原来内存中的数据移动到新的空间,但是难免会遇到不同情况:原有空间之后有足够大的空间,另一种则是原有空间之后没有足够大的空间,这就产生两种不同的解决方案,第一种是直接在原有内存之后追加空间,与拿来空间的数据不发生变化,第二种则是在对空间上另外找一个合适大小的来地址空间来使用,这样函数返回的是一个新的内存地址。

三。常见的动态内存错误

<1>对空指针的解应用操作

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

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

<4>使用free释放动态内存的一部分

<5>对同一动态内存多次释放

<6>动态开辟内存忘记释放(内存泄露)

四.堆空间和栈空间的常见几大错误

当我们学习完堆空间的内容后,我们知道在开辟动态内存空间后要记得及时释放其空间。C语言让我们在空间的开辟和释放上都有很大自主性,所以这也诞生了常见的两种内存上的错误,第一种是非法内存访问,第二种则是内存泄漏。第二种毫无疑问是由没有及时释放堆空间产生的后果,第一种则要细分成两类,其中的第一类是堆空间上的非法访问,当传值调用函数时,并且在堆上开辟空间时,我们都知道函数调用完其形参就会被操作系统回收掉,所以这样我们访问这块被回收的内存时就会产生非法访问;第二类则是栈空间上的的非法访问,当我们在接受了调用函数的返回值的地址后,对其进行访问,也会产生如下结果,与第一类同样原因。(当时我学习时曾经纳闷,后来明白函数返回值虽然可以被主函数中的变量接受,但是即使在接受后,其返回值地址原有的内容早已被操作系统回收,所以想通过其地址再去找内容是不可能的。)

注意两点是:一动态内存没有free释放是会一直在堆空间存在的,但是内存在栈空间的开辟当调用函数完毕后以及会被回收,二对于内存泄漏问题,当程序退出,内存泄漏的空间会强制被操作系统拿回来。

五.柔性数组的开辟与使用

1.柔性数组字如其名,就是说可以更改大小的数组。由于在结构体中最后一个元素允许是未知大小的数组,所以为柔性数组的出现埋下伏笔。这里我们会用malloc函数开辟空间,再用realloc函数进行修改动态内存大小,从而达到构建柔性数组的目的。

2.柔性数组的特点

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

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

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

3.两种形式的柔性数组

动态内存开辟空间会有一定的灵活性,我们可以要求其以结构体开辟一整块连续的内存在堆空间上,也可以通过动态开辟空间分别存储指针和内存,指针来寻找动态内存开辟的空间,内存是柔性数组可修改大小,这是两种不同的存储方式,也是两份不同的代码,当然也会有优缺点。首先先看代码:

//代码1 
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); 
//代码2 
typedef struct st_type 
{ 
int i; 
int *p_a; 
}type_a; 
type_a *p = (type_a *)malloc(sizeof(type_a)); 
p->i = 100; 
p->p_a = (int *)malloc(p->i*sizeof(int)); 
//业务处理 
for(i=0; i<100; i++) 
{ 
p->p_a[i] = i; 
} 
//释放空间 
free(p->p_a); 
p->p_a = NULL; 
free(p); 
p = NULL; 

我们不难发现第一种只有一次free,而第二种拥有两次free。差别在于此,对于计算机来说寄存器会获取空间数据(这里以堆空间来说),cpu又会去寄存器取得数据去完成各项指令,对于寄存器获取数据的根据有“局部性原理”一说,即当获取目标数据时往往也会获取目标周围的数据。因此第一种相对于第二种而言,在堆空间上是连续的一整块数据相比于分成两块的分散数据,第一种更能能够提高寄存器的效率,而这些获取的无效目标数据被称为内存碎片。不然寄存器只得向下寻找数据,经过高速缓存,经过内存,经过硬盘,但是越往下寻找其寻找数据的成本越高。终上,柔性数组的优势正如下文:

1.方便内存释放:第二种除了free掉结构体后,还是继续free掉结构体中的成员。

2.提高访问速度:连续内存的优势正在于此,而且能减少内存碎片。

四.再深刻理解malloc,free和内存管理

我们在使用free函数时,往往会发现其释放的字节实际比开辟字节数要多,所以开辟的字节数也绝对不仅只有程序员设计的一样,事实上,malloc在申请空间的时候,操作系统会给予更多的空间,其多余的空间是为了记录这次申请的更详细信息(比如申请了多大空间)。对于free函数,当free释放完空间后,不能堆其堆空间进行访问。这里所谓的释放,并不是销毁了其开辟的堆空间,而是改变了用来接受的指针与开辟堆空间的关系,就算指针仍然记得其堆空间的原有地址,但没有权限访问这块空间。这里的关系就是通过各种数据用来维护。对于p=NULL这句话来说,有无必要,因为编译器没有理由区域强制改为空指针,(提一下对于所有为被直接使用的空指针,其皆是空指针),这里p指向的内容取决于用户。曾有“在堆空间上开辟的越大越好,在栈上则要开辟的越发精巧”,C语言的动态内存管理体现在空间什么时候申请,申请多大空间,什么时候释放,释放多少这四个方面,可以说程序员+场景等价于内存管理,因为C语言底层工作由程序员决定较多。而对于java等语言,其直接使用即可。

以上就是栈区空间上的所有内容,若有不足请不惜赐教。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

一只爱喝coke的小鳄鱼

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

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

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

打赏作者

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

抵扣说明:

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

余额充值