游戏开发工具

C++RTTI机制下的对象内存模型(透彻)

上节所示的 Base 和 Derived 的对象内存模型非常简单,读者也很容易理解,它满足了 typeid 运算符在程序运行期间动态地获取表达式的类型信息的需求。在 C++ 中,除了 typeid 运算符,dynamic_cast 运算符和异常处理也依赖于 RTTI 机制,并且要能够通过派生类获取基类的信息,或者说要能够判断一个类是否是另一个类的基类,这样上节讲到的内存模型就不够用了,我们必须要在基类和派生类之间再增加一条绳索,把它们连接起来,形成一条通路,让程序在各个对象之间游走在面向对象的编程语言中,我们称此为继承链(Inheritance Chain)。

关于 dynamic_cast 运算符和异常处理我们将在后续章节中讲解,这里读者只需要知道它们依赖于 RTTI 机制。

将基类和派生类连接起来很容易,只需要在基类对象中增加一个指向派生类对象的指针,然而考虑到多继承、降低内存使用等诸多方面的因素,真正的对象内存模型比上节讲到的要复杂很多,并且不同的编译器有不同的实现(C++ 标准并没有对对象内存模型的细节做出规定)。

我们以下面的代码为例来展示 Visual C++ 下真正的对象内存模型:

class A
{
protected:
    int a1;
public:
    virtual int A_virt1();
    virtual int A_virt2();
    static void A_static1();
    void A_simple1();
};

class B
{
protected:
    int b1;
    int b2;
public:
    virtual int B_virt1();
    virtual int B_virt2();
};

class C: public A, public B
{
protected:
    int c1;
public:
    virtual int A_virt2();
    virtual int B_virt2();
};

最终的内存模型如下所示


1.jpg

从图中可以看出,对于有虚函数的类,内存模型中除了有虚函数表,还会额外增加好几个表,以维护当前类和基类的信息,空间上的开销不小。typeid(type).name() 方法返回的类名就来自“当前类的信息表”。

如果你希望深入了解上图的结构,请查看下面几篇文章:

1、VC++逆向:类、方法和 RTTI

2、RTTI结构详细分析(VC++)

3、C++中RTTI机制剖析

4、C++ dynamic_cast实现原理

5、dynamic_cast, RTTI, 整理


typeid 经过固定次数的间接转换返回 type_info 对象,间接次数不会随着继承层次的增加而增加,对效率的影响很小,读者可以放心使用。而 dynamic_cast 运算符和异常处理不仅要经过数次间接转换,还要遍历继承链,如果继承层次较深,那么它们的性能堪忧,读者应当谨慎使用!

类型是表达式的一个属性,不同的类型支持不同的操作,例如class Student类型的表达式可以调用 display() 方法,int类型的表达式就不行。类型对于编程语言来说非常重要,编译器内部有一个类型系统来维护表达式的各种信息。

在 C/C++ 中,变量、函数参数、函数返回值等在定义时都必须显式地指明类型,并且一旦指明类型后就不能再更改了,所以大部分表达式的类型都能够精确的推测出来,编译器在编译期间就能够搞定这些事情,这样的编程语言称为静态语言(Static Language)。除了 C/C++,典型的静态语言还有 Java、C#、Haskell、Scala 等。

静态语言在定义变量时通常需要显式地指明类型,并且在编译期间会拼尽全力来确定表达式的类型信息,只有在万不得已时才让程序等到运行后动态地获取类型信息(例如多态),这样做可以提高程序运行效率,降低内存消耗。

与静态语言(Static Language)相对的是动态语言(Dynamic Language)。动态语言在定义变量时往往不需要指明类型,并且变量的类型可以随时改变(赋给它不同类型的数据),编译器在编译期间也不容易确定表达式的类型信息,只能等到程序运行后再动态地获取。典型的动态语言有 JavaScript、Python、PHP、Perl、Ruby 等。

动态语言为了能够使用灵活,部署简单,往往是一边编译一边执行,模糊了传统的编译和运行的过程。例如 JavaScript 主要用来给网页添加各种特效(这是一种简单的理解),浏览器访问一个页面时会从服务器上下载 JavaScript 源文件,并负责编译和运行它。如果我们提前将 JavaScript 源码编译成可执行文件,那么这个文件就会比较大,下载就会更加耗时,结果就是网页打开速度非常慢,这在网络不发达的早期是不能忍受的。

总起来说,静态语言由于类型的限制会降低编码的速度,但是它的执行效率高,适合开发大型的、系统级的程序;动态语言则比较灵活,编码简单,部署容易,在 Web 开发中大显身手。