2.2 I386平台的内存布局
众所周知,I386是32位体系结构。因此对于绝大多数I386平台的C++编译器而言,sizeof(int)=sizeof(long)=sizeof(void*)=4。当然C++标准对此没有任何保证,我们也不应该试图编写依赖于此的代码。
1 代码及静态数据区
由代码加载器从动态链接库镜像(通常是exe或dll文件)加载,通常定位到镜像文件中指定的基址开始的内存区。如果基址所在内存已被占用,动态连接器会将代码或数据重定向到其它可用地址。
2 栈(Stack)
栈是最常用的动态数据存储区,所有函数的non-static对象和函数参数都在程序运行期在栈上分配内存。在数据结构中,术语“栈(Stack)”意指先进后出(FILO,First In Last Out),与“队列(Queue)”所指的FIFO相对。相对于基于堆的对象分配技术,默认使用栈的对象分配有两点优势:
一、栈的FILO与人的思维方式相同
现实生活中有许多事例都使用FILO的方式,比如人们必须先提起话筒再拨打号码,而后挂断电话之后再放下话筒。使用FILO的栈,可以保证事物的销毁顺序以其诞生顺序相反的顺序进行,不会产生在挂断电话之前就放下话筒的尴尬。
二、栈的分配管理仅需要两个额外指针:栈顶(esp)和栈底(ebp)指针
从实现的技术层面而言,栈的管理比其它动态分配技术要简单很多。I386平台上的动态栈管理,仅需要栈顶和栈底两个指针。这两个指针的存储显然不能放置于栈中,置于静态数据区又有损效率。I386平台为管理动态栈专门预留了两个通用寄存器变量esp与ebp,分别代表栈顶(esp,Extended Stack Pointer)与栈底(Extended Bottom Pointer)指针。其中的extended代表它们是32位指针,以区分16位的sp和bp寄存器。
3 堆(Heap)和自由存储区
栈中的变量对于分配与释放的顺序有特定要求,这在一定程度上限制了栈的适用范围。面向对象(OO,Object Oriented)的程序设计思想也要求能自由地控制变量的分配与销毁。由此,现代操作系统都提供了被称作“堆(Heap)”的自由存储区,以允许由程序员控制的对象创建和销毁过程。C标准库函数malloc和free则是对操作系统提供的堆操作的封装。C++提供的自由存储区运算符new和delete则通常是malloc和free的又一层封装。
操作系统经由malloc和free控制对堆的访问。堆的存储管理技术各不相同,简单的使用双链表管理,复杂的可以比拟一个完整的文件系统。
由于额外的管理需求,使用系统提供的通用分配器在堆上分配和销毁变量的代价,无论从空间角度还是效率角度而言,都比在栈上分配对象要高昂很多。对于sizeof上百的大型对象,这样的高昂代价还是可以接受的,但是对于sizeof只有个位数的小对象,这样的代价通常是一个数量级的差距。正因为这个原因,STL不使用new和delete,转而使用分配子(alllocor)分配对象。
这篇文章是 "CPP_Tricks" 系列文章的第 5 篇:
- C++ Tricks
- C++ Tricks 1.1 条件运算符(?:)
- C++ Tricks 1.2 逗号运算符(,)、逻辑运算符(&&,||)与运算符重载的陷阱
- C++ Tricks 2.1 X86概述
- C++ Tricks 2.2 I386平台的内存布局
- C++ Tricks 2.3 I386平台C函数内部的栈分配
- C++ Tricks 2.4 I386平台C函数调用边界的栈分配
- C++ Tricks 2.5 I386平台的边界对齐(Align)
- C++ Tricks 2.6 I386平台C函数的可变参数表(Variable Arguments)
- C++ Tricks 2.7 I386平台的其它函数调用模型
- C++ Tricks 3.1 左值右值与常量性(lvalue,rvalue & constant)
- C++ Tricks 3.2 标号、goto,以及switch的实现
Github Issue 留言
Disqus 留言