通过 Clang 查看 C++ 内存布局
发布时间:
如果你喜欢阅读 C++ 标准,可以跑到 Draft C++ Standard 网站上查看最新的 C++ 标准草案。 C++ 标准中关于内存模型和对象模型的描述主要在 [basic.memobj] 一节。 这里面有很多细节,比如生命周期、对象模型、对象布局等等。 但大部分时候学习者的需求很简单,我想实打实地看到编译器是如何处理我的代码的。 我不希望跑到运行期去通过调试器查看内存布局(我以前就是这么干的 XD), 我希望在编译期就能看到这些信息。我该怎么办呢?
我提供一种实践的思路:使用 Clang 的 -Xclang -fdump-record-layouts
选项。
GCC 当然也有 类似的选项, 只是处理起来比这种直接在网页上查看的方式要麻烦一些。
你可以跑到 Compiler Explorer 网站上选择 x86-64 Clang 作为编译器, 自己实现一份代码,通常为了学习 C++ 内存布局,我们会设计基类、派生类、虚函数等等, 例如:
cpp
class Base {
public:
int baseVar;
virtual void foo() {}
virtual void bar() {}
};
class Derived : public Base {
public:
int derivedVar;
void foo() override {}
void bar() override {}
};
int main() {
Derived d;
d.baseVar = 10;
d.derivedVar = 20;
d.foo();
d.bar();
return 0;
}
你会在输出栏看到相应的内存布局记录,这样就可以自己验证了:
shell
*** Dumping AST Record Layout
0 | class Base
0 | (Base vtable pointer)
8 | int baseVar
| [sizeof=16, dsize=12, align=8,
| nvsize=12, nvalign=8]
*** Dumping AST Record Layout
0 | class Derived
0 | class Base (primary base)
0 | (Base vtable pointer)
8 | int baseVar
12 | int derivedVar
| [sizeof=16, dsize=16, align=8,
| nvsize=16, nvalign=8]
除此以外,Clang 也会将中间代码生成(IRgen)的结果输出。
下面的回答来自 GitHub Copilot Chat:
这个输出是 C++ 编译器在处理类的内存布局时的详细信息。这是一个很好的工具,可以帮助你理解 C++ 如何在内存中布局类的实例。以下是对这个输出的详细解释:
0 | class Base
和0 | class Derived
:这两行表示Base
类和Derived
类的内存布局开始于偏移量0
.(Base vtable pointer)
:这是虚函数表(vtable)的指针。当一个类有虚函数时,编译器会为该类生成一个虚函数表,并在每个类的实例中添加一个指向该表的指针。这个指针在内存布局中的位置通常是在开始处,偏移量为 0.8 | int baseVar
和12 | int derivedVar
:这两行表示baseVar
和derivedVar
成员变量的内存布局。数字 8 和 12 是这些变量在类的内存布局中的偏移量。这些偏移量是相对于类的开始位置的。[sizeof=16, dsize=12, align=8, nvsize=12, nvalign=8]
和[sizeof=16, dsize=16, align=8, nvsize=16, nvalign=8]
:这些是类的一些属性。sizeof
是类的总大小,dsize
是类的数据成员的大小,align
是类的对齐要求,nvsize
是类的非虚部分的大小,nvalign
是类的非虚拟部分的对齐要求。0 | class Base (primary base)
:这一行表示Derived
类继承自Base
类,Base
类的内存布局在Derived
类的内存布局中开始于偏移量0
. 这意味着Derived
类的实例在内存中的布局首先包含Base
类的实例的布局。