• C/C++杂记:运行时类型识别(RTTI)与动态类型转换原理


    运行时类型识别(RTTI)的引入有三个作用:

    1. 配合typeid操作符的实现;
    2. 实现异常处理中catch的匹配过程;
    3. 实现动态类型转换dynamic_cast。

    1. typeid操作符的实现

    1.1. 静态类型的情形

    C++中支持使用typeid关键字获取对象类型信息,它的返回值类型是const std::type_info&,例:

    #include <typeinfo>
    #include <cassert>
    struct B {} b, c;
    struct D : B {} d;
    void test() {
        const std::type_info& tb = typeid(b); 
        const std::type_info& tc = typeid(c); 
        const std::type_info& td = typeid(d);
        assert(tb == tc);   // b和c具有相同的类型
        assert(&tb == &tc); // tb和tc引用的是相同的对象
        assert(tb != td);   // 虽然D是B的子类,但是b和d的类型却不同
        assert(&tb != &td); // tb和td引用的是不同的对象
    }

    理论上讲,编译器会为每一种类型生成一个能唯一标识该类型的类型信息对象,typeid返回的就是该对象的引用。

    通过查看clang编译器生成的LLVM汇编程序(LLVM汇编程序比本地汇编程序可读性较强),可以证明这一点。
    使用clang编译上述源码:“clang -S -emit-llvm test.cpp -o -”,生成LLVM汇编程序包含以下信息(为了方便阅读,省略了部分无关内容):

    @_ZTI1B = linkonce_odr constant { i8*, i8* } { ... }
    @_ZTI1D = linkonce_odr constant { i8*, i8*, i8* } { ... }
    
    define void @_Z4testv() #0 {
      %tb = alloca %"class.std::type_info"*, align 8
      %tc = alloca %"class.std::type_info"*, align 8
      %td = alloca %"class.std::type_info"*, align 8
      store bitcast ({ i8*, i8* }* @_ZTI1B to %"class.std::type_info"*), %tb, align 8
      store bitcast ({ i8*, i8* }* @_ZTI1B to %"class.std::type_info"*), %tc, align 8
      store bitcast ({ i8*, i8*, i8* }* @_ZTI1D to %"class.std::type_info"*), %td, align 8
      ...

    其中:

    • @_ZTI1B 和@_ZTI1D 是两个全局变量,用以存储std::type_info(或者其子类)对象。
    • 上述LLVM汇编程序中还列出了test()函数的起始部分内容,其中将@_ZTI1B 存储于%tb和%tc,将@_ZTI1D 存储于%td,正好对应原程序中的引用初始化语句。

    附加说明:

    • LLVM汇编语言也称之为LLVM中间表示(IR, Intermediate Representation),其中全局变量以“@”开头。详细请参见:LLVM Language Reference Manual。
    • _ZTI1B和_ZTI1D是经过名字修饰(name mangling)修饰之后的变量名,linux下可以使用c++filt命令还原成可读形式(例如:c++filt _ZTI1B输出“typeinfo for B”,说明_ZTI1B是标识B类型的全局变量)。

    1.2. 动态类型的情形

    当typeid的操作数引用的是一个动态类(含有虚函数的类) 类型时,它的返回值是被引用对象对应类型的类型信息对象,例:

    #include <typeinfo>
    #include <cassert>
    struct B { virtual void foo() {} };
    struct C { virtual void bar() {} };
    struct D : B, C {};
    void test() {
        D d;
        B& rb = d;
        C& rc = d;
        assert(typeid(rb) == typeid(d));  // rb引用的类型与d相同
        assert(typeid(rb) == typeid(rc)); // rb引用的类型与rc引用的类型相同
    }

    编译时可能还不知道rb或rc引用的类型,运行时怎么能判断该返回基类还是派生类对应的类型信息对象呢?

    还记得“C/C++杂记:深入虚表结构”一文中讲过的-fdump-class-hierarchy选项吧,用它将D的虚表打印出来如下:

    可见,无论是“主虚表”还是“次虚表”,其中的RTTI信息位置都是&_ZTI1D(即D类型对应的类型信息对象)。

    正是利用了这一点,运行时便可以通过vptr找到“虚函数表”,而“虚函数表”之前的一个位置存放了需要的类型信息对象,typeid可以直接返回这里的类型信息对象引用即可。
    下面的图示描述了这一过程:

    2. 实现异常处理中catch的匹配过程

    catch的匹配过程也可利用与typeid相似的原理进行类型匹配判断,此不再赘述。

    3. 动态类型转换(dynamic_cast)

    说明:本节不考虑虚拟继承的情形。

    先上一个例子:

    转换过程:
    (1) 对#2来说最为简单,首先获取RTTI对象,RTTI对象与目标类型信息对象一致,而偏移值也为0,所以只用返回源地址(pb)即可。
    (2) 对#1和#3来说,RTTI对象与目标类型信息对象一致,但是有偏移值-8,所以返回值为“(char*)pa + (-8)”或“(char*)pc + (-8)”。
    (3) 对#4来说,RTTI对象与目标类型信息对象不一致,但是目标类型C 是RTTI对象表示类型(D)是基类(后面会讨论如何判断继承关系),因此转换也是可行的。

    用clang编译上述源码,生成LLVM汇编程序如下(已作简化):

    @_ZTI1A= linkonce_odr constant { i8*, i8* } { ... }
    @_ZTI1B= linkonce_odr constant { i8*, i8* } { ... }
    @_ZTI1C= linkonce_odr constant { i8*, i8*, i8* } {..., i8* bitcast ({ i8*, i8* }* @_ZTI1A to i8*) }
    @_ZTI1D= linkonce_odr constant { i8*, i8*, i32, i32, i8*, i64, i8*, i64 } { ...,
            i8* bitcast ({ i8*, i8* }* @_ZTI1B to i8*), i64 2,
            i8* bitcast ({ i8*, i8*, i8* }* @_ZTI1C to i8*), i64 2050
        }

    从中可以看出,RTTI对象中存放的内容还包括基类的RTTI对象指针,成树状结构:

    因此继承关系可以通过此树状结构判断,有了继承关系,再递归从虚表中查找基类子对象在派生类中的偏移值,便可以确定最终返回地址。

    4. 参考

    (1) Itanium C++ ABI

    (2) LLVM Language Reference Manual

    (3) libc++abi源码(private_typeinfo.h文件

  • 相关阅读:
    分类与监督学习,朴素贝叶斯分类算法
    K-means算法应用:图片压缩
    聚类--K均值算法:自主实现与sklearn.cluster.KMeans调用
    numpy统计分布显示
    10.11作业numpy数据集练习
    9.29作业
    CAGradientlayer设置视图背景的渐变效果
    dyld: Library not loaded: @rpath/libswiftCore.dylib
    解读NSString之性能分析
    iOS UIButton超出父视图无法点击解决方法
  • 原文地址:https://www.cnblogs.com/malecrab/p/5574070.html
Copyright © 2020-2023  润新知