C++杂记:运行时类型识别(RTTI)与动态类型转换原理
运行时类型识别(RTTI)的引入有三个作用:
- 配合typeid操作符的实现;
- 实现异常处理中catch的匹配过程;
- 实现动态类型转换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文件)
http://www.cnblogs.com/malecrab/p/5574070.html
C++杂记:运行时类型识别(RTTI)与动态类型转换原理的更多相关文章
- C++学习之显式类型转换与运行时类型识别RTTI
static_cast const_cast reinterpret_cast 运行时类型识别(RTTI) dynamic_cast 哪种情况下dynamic_cast和static_cast使用的情 ...
- c++运行时类型识别(rtti)
一个简单运行时类型识别 namespace rtti_ex { /* * 类型信息基类 */ class i_type_info { public: // 判断是否是指定类型 bool is(cons ...
- C++之运行时类型识别RTTI
C++ Code 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849 ...
- C++——运行时类型识别RTTI
1.实现方式 typeid运算符,返回表达式的类型 dynamic_cast运算符,基类的指针或引用安全地转换成派生类的指针或引用 2.适用于:使用基类的指针或引用执行派生类的操作,且该操作不是虚函数 ...
- 运行时类型识别RTTI
1.RTTI的工作原理 例1. 用Class加载对象示例. package RTTI; public class Candy { static{ System.out.println("Lo ...
- Java基础之RTTI 运行时类型识别
运行时类型识别(RTTI, Run-Time Type Identification)是Java中非常有用的机制,在Java运行时,RTTI维护类的相关信息. 多态(polymorphism)是基于R ...
- C/C++杂记:运行时类型识别(RTTI)与动态类型转换原理
运行时类型识别(RTTI)的引入有三个作用: 配合typeid操作符的实现: 实现异常处理中catch的匹配过程: 实现动态类型转换dynamic_cast. 1. typeid操作符的实现 1.1. ...
- 【转载】C/C++杂记:运行时类型识别(RTTI)与动态类型转换原理
原文:C/C++杂记:运行时类型识别(RTTI)与动态类型转换原理 运行时类型识别(RTTI)的引入有三个作用: 配合typeid操作符的实现: 实现异常处理中catch的匹配过程: 实现动态类型转换 ...
- RTTI 运行时类型识别 及异常处理
RTTI 运行时类型识别 typeid ------ dynamic_cast dynamic_cast 注意事项: 1.只能应用于指针和引用之间的转化 2.要转换的类型中必须包含虚函数 3. ...
随机推荐
- [RxJS] Split an RxJS Observable into groups with groupBy
groupBy() is another RxJS operator to create higher order observables. In this lesson we will learn ...
- Android 比较两个时间段是否有交集或重复
先看一个例图: 在金山<电池管家>应用中就有一个类似上图这样的功能—— 开启多个定时任务. 当开启另一个定时任务的时候,如果即将开启的这个定时任务的时间段与已经开启了的定时任务的时间段有交 ...
- 【u017】请柬
Time Limit: 1 second Memory Limit: 128 MB [问题描述] 在电视时代,没有多少人观看戏剧表演.Malidinesia古董喜剧演员意识到这一事实,他们想宣传剧院, ...
- 【u010】银河英雄传说
Time Limit: 1 second Memory Limit: 128 MB [问题描述] 公元五八○一年,地球居民迁移至金牛座α第二行星,在那里发表银河联邦创立宣言,同年改元为宇宙历元年,并开 ...
- Java中sleep()与wait()区别
学习时正好碰到这两个方法,就查阅相关资料,并通过程序实现,进行区别一下: 1.每个对象都有一个锁来控制同步访问,Synchronized关键字可以和对象的锁交互,来实现同步方法或同步块.sleep() ...
- Git Push问题remote: hooks/update:10 undefined method 'require_relative' for main:Object(NomethodError)
今天在提交代码时遇到到了一个非常蛋疼的问题,remote: hooks/update:10 undefined method 'require_relative' for main:Object(No ...
- TCP三次握手和四次握手最直接的理解
网上有非常多文章讲TCP为什么建立连接时须要三次握手,关闭连接时须要四次握手.讲了非常多原理.反而让非常多人难以理解. 事实上仅仅有一句话:TCP连接是两个端点之间的事.因为TCP连接是可靠连接,所以 ...
- 两分钟读懂ThreadLocal
ThreadLocal的工作原理 场景 假设一个用户是一个线程.他们都对数据库进行操作,这个时候就会每个用户就会从数据源中开启一个事务以确保能够顺利的打开或者关闭事务.那么如何保证用户与用户之间的数据 ...
- android 流量统计
1 android通过架构流量统计TrafficStats类可以直接获得 获得总流量受理TrafficStats.getTotalRxBytes(), 获得总传出流量TrafficSt ...
- C#中的interface没那么简单
最近在园子里闲逛看到一篇文章“(抽象)类和接口细节分析”,尽管作者很细心很细致.可事实上C#里面的interface没那么简单,interface有着大量不为人知的小秘密的说. 1.值类型也能实现接口 ...