目录
参考,以及部分图引用于
https://www.cnblogs.com/dirichlet/p/3221066.htmlhttps://www.cnblogs.com/dirichlet/p/3221066.html
Visual C++ RTTI Inspectionhttps://blog.quarkslab.com/visual-c-rtti-inspection.html
https://www.cnblogs.com/QG-whz/p/4909359.htmlhttps://www.cnblogs.com/QG-whz/p/4909359.html
零、测试代码及内存布局
测试环境,MSVC2022,32位(gcc typeinfo存储位置有区别,见最后)
#include <iostream>
#include <typeinfo>
#include <string>
#include <rttidata.h>
using namespace std;
class BaseA
{
public:
BaseA(int i) :baseI(i) {};
virtual void print(void) { cout << "调用了虚函数Base::print()" << endl; }
virtual void setI() { cout << "调用了虚函数Base::setI()" << endl; }
virtual ~BaseA() {}
private:
int baseI;
};
class BaseB
{
public:
virtual void f() { }
virtual void f2() { }
virtual void f3() { }
};
class C : public BaseA, public BaseB
{
public:
C(int i):BaseA(i){}
virtual void f2() { cout << "调用了虚函数C::f2()" << endl; }
virtual void f3() { cout << "调用了虚函数C::f3()" << endl; }
};
int main()
{
C trueC(2);
C* c = &trueC;
BaseA* basea_C = (BaseA*)c;
BaseB* baseb_C = (BaseB*)c;
BaseB* trueB = new BaseB();
BaseB* trueB2 = new BaseB();
baseb_C->f2();
trueB->f2();
baseb_C->f3();
trueB->f3();
cout << "basea_C 指针值" << basea_C << endl;
cout << "baseb_C 指针值" << baseb_C << endl;
cout << "c 指针值" << c << endl;
// baseb_C的值 和 c差了 sizeof(BaseA)
cout << "sizeof(c)" << sizeof(BaseA) << endl;
const type_info* ti = &typeid(trueC);
cout << &typeid(trueC) << endl;
cout << typeid(trueC).name() << endl;
_s_RTTICompleteObjectLocator* cCOL = (_s_RTTICompleteObjectLocator*)(*((int*)(*((int*)(&trueC))) - 1));
_RTTIBaseClassArray* cBaseClassArray = cCOL->pClassDescriptor->pBaseClassArray;
for (int i = 0; i < cCOL->pClassDescriptor->numBaseClasses; i++)
{
// 输出C的所有基类名称
cout <<cBaseClassArray->arrayOfBaseClassDescriptors[i]->pTypeDescriptor->name<< endl;
}
_RTTIBaseClassDescriptor* d1 = cBaseClassArray->arrayOfBaseClassDescriptors[0];
_RTTIBaseClassDescriptor* d2 = cBaseClassArray->arrayOfBaseClassDescriptors[1];
_RTTIBaseClassDescriptor* d3 = cBaseClassArray->arrayOfBaseClassDescriptors[2];
BaseB* ptrb = &trueC;
const type_info& ti2 = typeid(*ptrb);
C* ssss = dynamic_cast<C*>(ptrb);
return 0;
}
1>class C size(12):
1> +---
1> 0 | +--- (base class BaseA)
1> 0 | | {vfptr}
1> 4 | | baseI
1> | +---
1> 8 | +--- (base class BaseB)
1> 8 | | {vfptr}
1> | +---
1> +---
1>C::$vftable@BaseA@:
1> | &C_meta
1> | 0
1> 0 | &BaseA::print
1> 1 | &BaseA::setI
1> 2 | &C::{dtor}
1>C::$vftable@BaseB@:
1> | -8
1> 0 | &BaseB::f
1> 1 | &C::f2
1> 2 | &C::f3
一、type_info与typeid
type_info
是一个标准库类型,位于头文件<typeinfo>
中。它用于存储类型信息,可以通过typeid
运算符获得。每个类型在程序运行期间都有一个唯一的type_info
对象,可以通过typeid
来查询。
type_info
提供了一些成员函数,可以用于比较类型信息、获取类型名称等。以下是一些常用的成员函数:
bool operator==(const type_info& rhs) const
:比较两个type_info
对象是否相等。bool operator!=(const type_info& rhs) const
:比较两个type_info
对象是否不相等。const char* name() const
:获取类型的名称,返回一个指向const char
的指针。size_t hash_code() const
:获取类型的哈希码,可以用于比较类型是否相等。
示例代码:
#include <iostream>
#include <typeinfo>
class Base {
public:
virtual ~Base() {}
};
class Derived : public Base {};
int main() {
Base* base_ptr = new Derived;
const std::type_info& base_type = typeid(*base_ptr);
const std::type_info& derived_type = typeid(Derived);
if (base_type == derived_type) {
std::cout << "base_ptr points to a Derived object\n";
}
else {
std::cout << "base_ptr does not point to a Derived object\n";
}
return 0;
}
基类有虚函数的情况下,typeid返回真实实例的类型
二、Type_info的存储位置
验证
(_s_RTTICompleteObjectLocator*)(*((int*)(*((int*)(&trueC))) - 1));
这行代码取到虚表上面那个位置指向的内容。
c2指向 虚表[-1]指向的_s_RTTICompleteObjectLocator对象,可以看到其pTypeDescriptor成员的值和 ti(typeid(c)的地址)的值是一样。
三、RTTI信息
此处引用两张图
- 一张图说明了结构体的信息及布局
- 另一张图说明了,这些信息位于rdata节,编译期生成
测试代码中通过访问RTTI信息,打印了类C的所有基类名称。
四、dynamic_cast底层代码
知道了RTTI信息的结构,那么dynamic_cast具体是怎么实现的呢?
调试dynamic_cast的反汇编,进入函数 __RTDynamicCast,不断f11,最后发现代码位置于rtti.cpp中。
我的位置:C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.34.31933\crt\src\vcruntime\rtti.cpp
extern "C" void * __CLRCALL_OR_CDECL __RTDynamicCast (
void * inptr, // Pointer to polymorphic object
LONG VfDelta, // Offset of vfptr in object
void * srcVoid, // Static type of object pointed to by inptr
void * targetVoid, // Desired result of cast
BOOL isReference) // TRUE if input is reference, FALSE if input is ptr
noexcept(false)
这函数代码不少,确实dynamic_cast不能滥用。。。具体就是去根据上面的RTTI信息去查找,这里面还涉及到查找顺序,类的可见性等,具体可参考源码。
五、typeid底层代码
同样位于rtti.cpp,主要是针对测试的多态类会进这个,其他好像编译期就确定了?
extern "C" void * __CLRCALL_OR_CDECL __RTtypeid (
void * inptr) // Pointer to polymorphic object
noexcept(false)
六、虚函数调用原理
可以对比下C的实例和BaseB实例调用同一个虚函数,如下图:
- C实例中baseb指向的虚表和B实例中指向的虚表不是同一个
-
C实例中baseb指向的虚表,用C的函数地址覆盖了
-
对比反汇编可以看到,baseb_C调用f2和trueB调用f2的代码是一样的只是this指针的值不一样,进而指向了不同的虚函数表,实现了多态
-
对比调用不同的函数,其实就是函数在虚表中的偏移不样一个 +4字节,一个 +8字节
因为是32位,所以指针偏移是4字节
附 gcc下的typeinfo位置
gcc下测试,typeinfo应该直接就是虚表[-1]指向的位置https://www.recon.cx/2012/schedule/attachments/45_Recon%202012%20Skochinsky%20Compiler%20Internals.pdf