皇媛世家嫩白修复液本文将会介绍 C++ 对象几种不同的内存布局,并探讨不同的存储和读取标识符会如何影响内存占用。本文不会涉及任何编译器增强代码、名称修饰等内存相关的 C++ 机制,这些内容属于编译器和架构相关内容。为了进一步简化内容,本文只考虑标准的栈内存延伸方向(即从上向下),且内存中的数据成员也以倒置形式表示。
简言之,本文是关于 不同的对象是如何在内存中存储 的。
上面的例子要说明的是:只有数据成员对象会被分配在栈上,而且排列的顺序也与它们在代码中的声明顺序相同。绝大多数编译器的行为都是这样的。
此外,所有的成员函数、构造函数、析构函数以及编译器增强代码都在代码段中( text segment )。这些函数都会通过一个隐式传入的
如上所示,所有的非静态成员变量都和前面的例子一样,按照声明顺序从上到下排列在栈中。
静态成员变量的内存会被分配在数据段( data segment )。在代码中,想要访问静态成员变量需要使用作用域解析运算符(
),但在编译好的代码中却是没有作用域和命名空间的。这是因为编译器通过名称修饰( name managling )来让所有对这些变量的访问都变为通过绝对或相对地址进行。关于名称修饰的信息可以阅读 IBM 文章IBM Documentation。
静态函数会被放在代码段中,可以使用作用域运算符进行调用。静态函数的参数表中并不会包含
对于 virtual 关键字,编译器会自动为其在虚拟表(virtual table) 中插入一个指针(
),该指针在内存中和对象放在一起。所以对于 virtual 函数的直接调用会被转换为间接调用(即在virtual 函数内部是如何运行的?一文中提到过的动态调度)。通常,编译器会为每个类在数据段中静态地创建虚拟表,但实际情况取决于具体的编译器实现。
对象的指针,该对象包含与当前类及基类(若存在)的 DAG (有向无环图,Directed Acyclic Graph )相关信息。
在继承模型中,基类和成员变量类都属于派生类的子对象。因此派生类对象的内存布局是像上面这样的。
不管后续的派生关系如何,不可变区域中的数据都会和对象的起始点维持一个固定的偏移值
共享区域会包含一个虚基类,其位置会受到后续派生和具体的派生顺序的影响。具体内容参见该文:How Does Virtual Base Class Works Internally? – Vishal Chovatiya
|