c++ new数组_C++|内存管理|数组内存分配机制

本文参考Effective C++与编译器源码
引言:你是否想过数组和指针为什么sizeof不同,你是否想过为什么new[]需要指定长度,而delete[]不需要,你是否质疑过为什么传数组一定要顺带传大小,你是否还以为堆上一定分配着数组大小?
以下为您深(浅)入探索C++中的内存模型。

本文内容为自己的读书笔记+实验,如无泛用性,杠精退散。

数组-》指针,退化之路

int 

这两者有什么不同呢?显然,对a进行sizeof,大小应该是100,而b则是8,也就是说,数组显然存在着某种额外的信息,告诉着你数组的大小。

很多无水平的教科书会对数组和指针进行混淆,事实上,由数组在传参中转化为指针的过程是一种退化,丢失了大小信息。

然而,这种退化并不是万能的

int 

你会发现,如果数组和指针可以任意转化的话,应该是能匹配的,然而,事实上却完全不可。

因为在a[1]这样的过程中,计算a的偏移量是依赖于元素的大小的,int**对象+1的偏移量会是int*的大小,而不是int[5]的大小. 而对于int*和 int[5]而言,他们的元素是一样的int。

总而言之,退化只能退化顶层的数组。


如何存储数组的大小

  1. 对于栈中的自动对象,int a[5]等,直接由编译器提供大小,作为一种立即数直接参与汇编码中,这也是为什么栈数组必须使用常数的缘故,因为作为代码的一部分这必须是编译期间已知的。
  2. 对于堆上的内置类型或POD结构体(int,char等等),不存储大小,因为编译器根本无需析构,也没有必要知道数组具体的大小。内存的释放由malloc/free存储的字节大小处理即可。
  3. 对于堆上有构造或者析构函数的对象,存储大小有两种典型方式。一种是在分配的对象前一段内存处分配size_t的大小存储大小,另一种则是用关联数组,对将地址和对应的大小进行关联。前者实现简便,后者则避免了内存修改导致大小被污染的风险。

事实上,很多人都有这样的误解,即所有数组前面都存放着大小,然而看了这一段,你会发现编译器很聪明,不会把内存浪费在无意义的地方。


new[]的流程解析

new的操作看似简单,实际上却由编译器进行重排,内联展开后插入很多隐藏的代码

1.判断数据类型
2.计算内存大小(依据1中是否需要存储大小给予额外的空间)
3.new_array函数直接调用new_scalar(事实上你的[]并没有实际作用,
仅仅是一种提示,真正的改变是由编译器的额外代码完成的)
4.new_scalar调用系统的malloc函数
5.malloc函数查找到空余内存,开辟一段chunk,将chunk标记为已使用,然后记录chunk大小。(依赖于系统)
6.返回chunk的首指针
7.如果1中判断需要进行析构或者构造,则首先存储大小,再让指针加上一段偏移量,
对于最终的指针,根据对象的大小和数量对于分配后每段内存进行对应的构造。
8.返回(偏移后)的指针。

可以看出,事实上malloc的大小会根据编译器对于数据类型的识别而改变,所以不能轻易地把所有的数组都当做存储大小混为一谈。在new[]操作符中,一部分内存用于存储数组大小;而在malloc操作符中,一部分内存用于存储字节大小。关于malloc的实现,请关注日后的专栏更新。

以上就是对于new[]操作符经过对MSVC的源代码进行了一定的解读之后,我的一些理解。鉴于很多知乎答主对于堆上数组大小分配在哪里不是很清晰,包括很多知名的答主都存在一定的混淆,我分类讨论,对于易混淆点做出了一定的辨析,希望知友能理解更深!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值