



C++是一种多范式编程语言,该语言的核心之一是对象。在C++中,对象具有如下属性。
● 大小(由sizeof获得该属性)。
● 对齐限制(由alignof获得该属性)。
● 存储期(分为automatic、static、dynamic、thread-local)。
● 生命周期。
● 类型。
● 值。
● 名称(可选)。
“C++对象模型”是C++语言中一个非常重要的概念,它定义了内存中对象的布局和访问方式,包括虚函数、多继承、模板等特性的实现方式,对于理解C++程序的底层实现和进行高效的C++编程有着非常重要的意义。因此,每一个C++程序员都有必要了解C++对象模型的相关知识。
C++对象模型的一个关键特征是虚函数。一个类的虚函数成员是指以virtual开头声明的函数,例如在类Q中声明一个print虚函数:
class Q {
public:
virtual ~Q() = default;
virtual void print();
};
C++中多态的实现需要保证一个类继承了另一个声明有虚函数的类,并且继承类型为public,例如类Z继承自类Q:
class Z : public Q {
public:
void print() override;
};
对于拥有虚函数的类而言,对象模型需要考虑的问题包括虚函数存放的位置、以何种方式存放、如何调用相应的虚函数等。同时,C++对象模型还需要考虑虚函数在多态场景下的实现。
根据“C++之父”本贾尼·斯特劳斯特卢普(Bjarne Stroustrup)的《C++语言的设计和演化》一书,在实现C++多态时,编译器会将对象中的虚函数的地址搜集起来并放到一个表格中,然后在对象中增加一个成员vptr来指向这个拥有虚函数地址的表格;对于类的其他成员函数(包括静态成员函数和非静态成员函数),编译器将其放置在对象内存(即栈或堆中为了存放该对象而分配的内存)之外的某处,一般为进程的代码区;对于定义在类中的非静态数据成员,编译器将其放置在对象内存之中;对于静态数据成员,编译器将其放置在对象内存之外的某处,一般为进程的数据区。例如,更改类Q的定义如下:
class Q {
public:
virtual ~Q() = default;
virtual void print();
void print(int);
static void need(int);
private:
static int a_;
int b_;
};
按照本贾尼所设想的内存模型规则,类Q的对象的内存布局如图1-1所示。
图1-1
为了实现多态,C++又引进了运行时类型识别(Run Time Type Identification,RTTI),此时若类Z公共继承自类Q,则类Z的对象的内存布局如图1-2所示。
图1-2
随着不同编译器的出现,相应的C++对象模型的实现也有所变化,但总体上均是基于本贾尼设计的方案进行变动。
本书主要研究的是GCC/Clang编译器针对C++对象模型的实现。在GCC和Clang编译器中,C++对象模型均是基于Itanium C++ ABI实现的,因此GCC中的C++对象模型的实现均可应用于Clang编译器。本书将围绕GCC展开,讲解其C++对象模型的实现。