C++|内存管理|数组内存分配机制
2023-03-15 22:02:29 时间
本文参考Effective C++与编译器源码
引言:你是否想过数组和指针为什么sizeof不同,你是否想过为什么new[]需要指定长度,而delete[]不需要,你是否质疑过为什么传数组一定要顺带传大小,你是否还以为堆上一定分配着数组大小? 以下为您深(浅)入探索C++中的内存模型。
本文内容为自己的读书笔记+实验,如无泛用性,杠精退散。
数组-》指针,退化之路
int a[5][5];
int **b = new int* [5];
这两者有什么不同呢?显然,对a进行sizeof,大小应该是100,而b则是8,也就是说,数组显然存在着某种额外的信息,告诉着你数组的大小。
很多无水平的教科书会对数组和指针进行混淆,事实上,由数组在传参中转化为指针的过程是一种退化,丢失了大小信息。
然而,这种退化并不是万能的
int a[5][5];
void fun(int ** para);
你会发现,如果数组和指针可以任意转化的话,应该是能匹配的,然而,事实上却完全不可。
因为在a[1]这样的过程中,计算a的偏移量是依赖于元素的大小的,int**对象+1的偏移量会是int*的大小,而不是int[5]的大小. 而对于int*和 int[5]而言,他们的元素是一样的int。
总而言之,退化只能退化顶层的数组。
如何存储数组的大小
- 对于栈中的自动对象,int a[5]等,直接由编译器提供大小,作为一种立即数直接参与汇编码中,这也是为什么栈数组必须使用常数的缘故,因为作为代码的一部分这必须是编译期间已知的。
- 对于堆上的内置类型或POD结构体(int,char等等),不存储大小,因为编译器根本无需析构,也没有必要知道数组具体的大小。内存的释放由malloc/free存储的字节大小处理即可。
- 对于堆上有构造或者析构函数的对象,存储大小有两种典型方式。一种是在分配的对象前一段内存处分配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的实现。
相关文章
- 金融服务领域的大数据:即时分析
- 影响大数据、机器学习和人工智能未来发展的8个因素
- 从0开始构建一个属于你自己的PHP框架
- 如何将Hadoop集成到工作流程中?这6个优秀实践必看
- SEO公司使用大数据优化其模型的5种方法
- 关于Web Workers你需要了解的七件事
- 深入理解HTTPS原理、过程与实践
- 增强分析:数据和分析的未来
- PHP协程实现过程详解
- AI专家:大数据知识图谱——实战经验总结
- 关于PHP的错误机制总结
- 利用数据分析量化协同过滤算法的两大常见难题
- 怎么做大数据工作流调度系统?大厂架构师一语点破!
- 2019大数据处理必备的十大工具,从Linux到架构师必修
- OpenCV中的KMeans算法介绍与应用
- 教大家如果搭建一套phpstorm+wamp+xdebug调试PHP的环境
- CentOS下三种PHP拓展安装方法
- Go语言HTTP Server源码分析
- Go语言HTTP Server源码分析
- 2017年4月编程语言排行榜:Hack首次进入前五十