The introduction of runtime type Recognition (RTTI) has three functions:
- With the implementation of the typeid operator;
- Realizing the matching process of catch in exception handling;
- Implements the dynamic type conversion dynamic_cast.
1. Implementation of the typeid operator
1.1. Static types of situations
C + + supports the use of the typeID keyword for object type information, and its return value type is 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 and C have the same type assert (&TB = = &TC);//TB and TC refer to the same object assert (TB! = TD); Although D is a subclass of B, the types of B and D are different assert (&TB! = &TD);//TB and TD refer to different objects}
In theory, the compiler generates a type information object that uniquely identifies the type for each type, and typeID returns a reference to that object.
This can be demonstrated by looking at the LLVM assembler generated by the Clang compiler (the LLVM assembler is more readable than the local assembler).
Compile the above source code using clang: "Clang-s-emit-llvm test.cpp-o-", Generate LLVM assembler contains the following information (for readability, omit some extraneous content):
@_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 @_zti1b%tb, align 8 @_zti1b
%TC, align 8 @_zti1d%td, align 8 ...
which
- @_zti1b and @_zti1d are two global variables that store std::type_info (or its subclasses) objects.
- The LLVM assembler also lists the starting part of the test () function, where @_zti1b is stored in%TB and%TC, @_zti1d is stored in%td, and corresponds to the reference initialization statement in the original program.
Additional Instructions:
- LLVM assembly language is also known as LLVM Intermediate representation (IR, intermediate representation), where global variables begin with "@". For details, see: LLVM Language Reference Manual.
- _ZTI1B and _ZTI1D are variable names after name modification (name mangling), and Linux can be restored to readable form using the c++filt command (for example: c++filt _zti1b output "TypeInfo for B", Description _ ZTI1B is a global variable that identifies type B).
1.2. Dynamic types of situations
When the operand of typeid refers to a dynamic class (a class that contains virtual functions), its return value is the type information object of the corresponding type of the referenced object, for example:
#include <typeinfo> #include <cassert>struct B {virtual void foo () {}};struct C {virtual void bar () {}};str UCT d:b, C {};void test () { d D; b& RB = D; c& rc = D; ASSERT (typeID (RB) = = typeID (d)); The type of RB reference is the same as D assert (typeID (RB) = = typeID (RC));//RB refers to the same type as the RC reference}
At compile time you may not know the type of the RB or RC reference, how can the runtime determine whether the return base class or the derived class corresponds to the type information object?
Remember the-fdump-class-hierarchy option in the article "C + + notes: Deep virtual table Structure", which prints out the virtual table of D as follows:
Visible, whether it is the "main virtual table" or "Secondary virtual table", where the Rtti information location is &_zti1d (that is, the type of D-type information object).
With this in mind, the runtime can find the "virtual function table" by Vptr, and a location before the virtual function table holds the required type information object, typeID can directly return the type Information object reference here.
The following illustration describes this process:
2. Implementation of catch matching process in exception handling
The matching process of catch can also be used to determine the type matching principle similar to typeID, which is no longer mentioned.
3. Dynamic type conversion (dynamic_cast)
Description: This section does not consider the case of virtual inheritance.
Let's start with an example:
Conversion process:
(1) For the simplest, first gets the Rtti object, the Rtti object is consistent with the target type information object, and the offset value is 0, so only the return source address (PB) can be used.
(2) for # # and # #, the Rtti object is consistent with the target type information object, but has an offset value of 8, so the return value is "(char*) PA + (-8)" or "(char*) PC + (-8)".
(3) For # #, the Rtti object is inconsistent with the target type information object, but the target type C is the Rtti object representation type (D) is the base class (which later discusses how to determine the inheritance relationship), so the conversion is also possible.
Compile the above source with clang, generate the LLVM assembler as follows (Simplified):
@_zti1a= LINKONCE_ODR constant {i8*, i8*} {...} @_zti1b= LINKONCE_ODR constant {i8*, i8*} {...} @_zti1c @_zti1a to i8*)}@_zti1d= LINKONCE_ODR constant {i8*, i8*, I32, I32, i8*, i64, i8*, i64} {...,
@_ZTI1B to i8*), i64 2,
@_zti1c to i8*), I64 2050 }
As you can see, the contents of the Rtti object also include the base class's Rtti object pointers, which are tree-like structures:
Therefore, the inheritance relationship can be judged by this tree structure, with the inheritance relationship, and then recursively find the base class sub-object in the derived class of the offset value from the virtual table, you can determine the final return address.
4. Reference
(1) Itanium C + + ABI
(2) LLVM Language Reference Manual
(3) Libc++abi source code (private_typeinfo.h file)
C + + notes: Run-time type recognition (RTTI) and dynamic type conversion principles