打印 C++ 类的内存排布

Table Of Contents

C++ 类的各个成员在内存中的布局,一般是通过理论推导和各种旁敲侧击的手段来大致得出的。实际上,我们可以获得更全面,更确信的材料:让编译器直接告诉我们某个类在内存中的实际排布。

MSVC

在 Visual Studio 中添加 /d1 reportAllClassLayout/d1 reportSingleClassLayout<类名> 这两个编译参数:前者打印所有类,后者打印某一个类。以此代码为例:

 1class Base {
 2public:
 3    Base() : a(0) {}
 4    virtual ~Base() {};
 5
 6    virtual void Func1() {};
 7    virtual void Func2() {};
 8
 9    void Func3() {};
10private:
11    int a;
12};
13
14class Sub : public Base {
15public:
16    Sub() : b(0) {}
17    ~Sub() {};
18
19    virtual void Func1() {};
20
21    void Func4() {};
22private:
23    int b;
24};

编译后,类的内存布局会作为编译信息的一部分输出到标准输出,并以字符画的形式呈现:

 1class Base	size(8):
 2	+---
 3 0	| {vfptr}
 4 4	| a
 5	+---
 6
 7Base::$vftable@:
 8	| &Base_meta
 9	|  0
10 0	| &Base::{dtor} 
11 1	| &Base::Func1 
12 2	| &Base::Func2 
13
14Base::{dtor} this adjustor: 0
15Base::Func1 this adjustor: 0
16Base::Func2 this adjustor: 0
17Base::__delDtor this adjustor: 0
18Base::__vecDelDtor this adjustor: 0
19
20class Sub	size(12):
21	+---
22 0	| +--- (base class Base)
23 0	| | {vfptr}
24 4	| | a
25	| +---
26 8	| b
27	+---
28
29Sub::$vftable@:
30	| &Sub_meta
31	|  0
32 0	| &Sub::{dtor} 
33 1	| &Sub::Func1 
34 2	| &Base::Func2 
35
36Sub::{dtor} this adjustor: 0
37Sub::Func1 this adjustor: 0
38Sub::__delDtor this adjustor: 0
39Sub::__vecDelDtor this adjustor: 0

相较而言最为清晰直观。其他操作系统的用户可借助在线的编译平台(如 GCC Godbolt )来体验。

参考:《Visual Studio 打印类的内存布局》 ,源自 imJaron 的 CSDN 博客

GCC

1# GCC 版本 < 8
2g++-7 -c -fdump-class-hierarchy ./main.cpp
3# GCC 版本 >= 8
4g++-8 -c -fdump-lang-class ./main.cpp

两者都会生成 main.cpp.xxx.class 文件:

 1Vtable for Base
 2Base::_ZTV4Base: 6 entries
 30     (int (*)(...))0
 48     (int (*)(...))(& _ZTI4Base)
 516    (int (*)(...))Base::~Base
 624    (int (*)(...))Base::~Base
 732    (int (*)(...))Base::Func1
 840    (int (*)(...))Base::Func2
 9
10Class Base
11   size=16 align=8
12   base size=12 base align=8
13Base (0x0x7ffcd48d4960) 0
14    vptr=((& Base::_ZTV4Base) + 16)
15
16Vtable for Sub
17Sub::_ZTV3Sub: 6 entries
180     (int (*)(...))0
198     (int (*)(...))(& _ZTI3Sub)
2016    (int (*)(...))Sub::~Sub
2124    (int (*)(...))Sub::~Sub
2232    (int (*)(...))Sub::Func1
2340    (int (*)(...))Base::Func2
24
25Class Sub
26   size=16 align=8
27   base size=16 base align=8
28Sub (0x0x7ffcd477d1a0) 0
29    vptr=((& Sub::_ZTV3Sub) + 16)
30  Base (0x0x7ffcd48d4e40) 0
31      primary-for Sub (0x0x7ffcd477d1a0)

Emm……不太好解读。虚函数表比较详细,但似乎没有成员变量的信息。

参考:GCC online documentation for releases 7.5 and 8.4 .

Clang

注意:上述代码只有类的定义,在这里需要作出修改,要包含程序实际执行的部分,并且在执行流中使用到要打印的类,否则无法生成该类的内存布局。

1# 只尝试了 clang-7 和 clang-9
2clang-7 -cc1 -emit-llvm -fdump-record-layouts ./main.cpp
3clang-7 -cc1 -emit-llvm -fdump-vtable-layouts ./main.cpp

内存排布信息输出在标准输出,比较长也比较复杂,这里就不贴了。

参考:


【C++ 小问答】3:未定义的 `Constexpr` 函数
【C++ 小问答】4:数值运算中的小类型隐式转换