C++类内存布局由编译器严格决定:单一继承时基类在前、成员按序排列;含虚函数则对象首部有vptr指向vtable;多重继承中非最左基类需this偏移;虚继承引入vbptr和动态偏移,增加开销。

在 C++ 中,类的内存布局不是黑箱,而是由编译器依据标准和实现细节严格决定的。理解它,是掌握多态、继承、虚函数调用、指针偏移等底层行为的关键——它直接决定了 this 指针的值、static_cast 和 reinterpret_cast 的安全性,以及为什么某些对象不能简单 memcpy。
单一继承下的内存布局:数据成员顺序即内存顺序
非虚继承时,子类对象内存中依次排布:基类部分(按继承顺序)、自身成员变量(按声明顺序)。没有额外开销,也没有“间隙”(除非对齐要求插入填充字节)。
- 基类子对象总位于对象起始地址(即 &obj == &obj.base)
- 成员变量布局与声明顺序完全一致,编译器不会重排(除非开启特定优化且不改变可观察行为)
- 空基类优化(EBO)可能使空基类不占空间,但其地址仍合法(通常复用子类首个成员地址)
虚函数表(vtable)与虚函数指针(vptr)的位置
含虚函数的类,编译器会在对象最前面插入一个隐式的 vptr(通常为指针大小,如 8 字节),指向全局只读的虚函数表(vtable)。vtable 本身不存于对象内,而是编译期生成的静态数据结构。
- vptr 是每个对象的私有成员,不是类共享的;多态对象切片时会丢失派生类 vptr
- vtable 条目顺序 = 虚函数首次声明顺序(包括从基类继承来的虚函数,若被重写则填入派生类版本)
- 多重继承中,非最左基类子对象的起始地址 ≠ 整个对象起始地址,其 vptr 位于各自子对象头部
多重继承与指针调整:this 指针不再是简单的地址
当一个类从多个非虚基类继承时,各基类子对象在内存中并列排布。访问不同基类接口时,this 指针需做偏移调整——这由编译器在调用虚函数或进行 static_cast 时自动插入加减指令完成。
立即学习“C++免费学习笔记(深入)”;
- 最左基类子对象与派生类对象地址相同;其余基类子对象地址 = 对象首地址 + 偏移量
- 虚函数调用通过 vtable 查找函数地址后,若该函数属于非最左基类,则编译器额外生成 this 调整代码(常见于 vtable 条目中存储“thunk”跳转桩)
-
static_cast不是类型擦除,而是计算并返回 Base2 子对象的正确地址(&d)
虚继承:解决菱形继承歧义,引入虚基类指针(vbptr)
虚继承使共享基类只出现一次。为此,派生类中会添加 vbptr(指向虚基类表),并在运行时动态计算虚基类子对象的偏移。这带来额外空间与时间开销。
- 虚基类子对象不一定在对象开头,也不一定紧邻某基类;其位置由最终派生类决定
- vbptr 通常放在对象开始处(在 vptr 之后)或末尾,具体取决于编译器(如 MSVC 放开头,Itanium ABI 常放末尾)
- 访问虚基类成员需两次间接寻址:先查 vbtable 得偏移,再加到 this 上定位成员——比普通继承慢











