【转载】C/C++杂记:运行时类型识别(RTTI)与动态类型转换原理
原文:C/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文件)
【转载】C/C++杂记:运行时类型识别(RTTI)与动态类型转换原理的更多相关文章
- c++运行时类型识别(rtti)
一个简单运行时类型识别 namespace rtti_ex { /* * 类型信息基类 */ class i_type_info { public: // 判断是否是指定类型 bool is(cons ...
- C++之运行时类型识别RTTI
C++ Code 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849 ...
- C++学习之显式类型转换与运行时类型识别RTTI
static_cast const_cast reinterpret_cast 运行时类型识别(RTTI) dynamic_cast 哪种情况下dynamic_cast和static_cast使用的情 ...
- 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++杂记:运行时类型识别(RTTI)与动态类型转换原理
运行时类型识别(RTTI)的引入有三个作用: 配合typeid操作符的实现: 实现异常处理中catch的匹配过程: 实现动态类型转换dynamic_cast. 1. typeid操作符的实现 1.1. ...
- RTTI 运行时类型识别 及异常处理
RTTI 运行时类型识别 typeid ------ dynamic_cast dynamic_cast 注意事项: 1.只能应用于指针和引用之间的转化 2.要转换的类型中必须包含虚函数 3. ...
随机推荐
- OpenStack高可用方案及配置
1 OpenStack高可用介绍 1.1 无状态和有状态服务 无状态服务指的是该服务接收的请求前后之间没有相关关系,接收并处理完该请求后不保存任何状态,在OpenStack的服务中常见的无状态服务 ...
- 自定义控件(视图)2期笔记13:View的滑动冲突之 内部拦截法
1. 内部拦截法: 父容器不拦截事件,所有的事件全部都传递给子元素,如果子元素需要此事件就直接消耗掉,否则就交给父容器进行处理. 这种方法和Android中的事件分发机制不一样,需要配合request ...
- UVA10125 Sumsets
嘟嘟嘟 很简单的折半搜索. 把式子变一下型,得到\(a + b = d - c\). 然后枚举\(a, b\),存到\(map\)里,再枚举\(c, d\)就好了. \(map\)以\(a,b\)两数 ...
- Android的JNI调用(二)
Android Studio 2.3在native下已经有了代码提示功能,按照提示下载相应组件就可以debug native代码. 一.Java调用JNI与JNI调用Java 1.1 C调用Java ...
- oracle数据库每天自动备份
现在介绍一下我们项目中使用exp工具每天自动对oracle进行备份. 用这个工具我们可以使用命令行的方式也可以使用pl/sql developer来进行导出. 我们要使用exp命令来导出oracle的 ...
- [iOS]为git设置代理
查看本地git配置信息 git config --global -e 查看自己***的代理地址和端口信息 为git添加代理 git config --global http.proxy https:/ ...
- java基础需要掌握的内容
一.Java的基本程序设计结构 二.对象与类 三.继承 四.接口.lambda表达式与内部类 五.异常,断言与日志 六.泛型程序设计 七.集合 八.并发(线程) 九.输入与输出(IO流) 十.网络 十 ...
- JavaScript变量类型检测总结
JavaScript中的变量类型: 基本类型值:Undefined,Null,Boolean,Number和String. 按值访问(可直接操作保存在变量中的变量值): 复制规则:当复制基本类型值时: ...
- ABAP术语-V1 Module
V1 Module 原文;http://www.cnblogs.com/qiangsheng/archive/2008/03/21/1115707.html Function module creat ...
- Mysql-表的基本操作
一 存储引擎介绍 二 表介绍 三 创建表 四 查看表结构 五 数据类型 六 表完整性约束 七 修改表ALTER TABLE 八 复制表 九 删除表 一 .存储引擎介绍 存储引擎即表类型,mysql根据 ...