c++内存布局、typeid、RTTI、dynamic_cast原理、虚函数调用原理串联一下

目录

 零、测试代码及内存布局

 一、type_info与typeid

二、Type_info的存储位置

三、RTTI信息

四、dynamic_cast底层代码 

五、typeid底层代码

六、虚函数调用原理

附 gcc下的typeinfo位置:


参考,以及部分图引用于

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实例调用同一个虚函数,如下图:

  1. C实例中baseb指向的虚表和B实例中指向的虚表不是同一个
  2. C实例中baseb指向的虚表,用C的函数地址覆盖了

  3. 对比反汇编可以看到,baseb_C调用f2和trueB调用f2的代码是一样的只是this指针的值不一样,进而指向了不同的虚函数表,实现了多态

  4. 对比调用不同的函数,其实就是函数在虚表中的偏移不样一个 +4字节,一个 +8字节

        因为是32位,所以指针偏移是4字节

 

附 gcc下的typeinfo位置

        gcc下测试,typeinfo应该直接就是虚表[-1]指向的位置https://www.recon.cx/2012/schedule/attachments/45_Recon%202012%20Skochinsky%20Compiler%20Internals.pdf

 

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值