C++ 内存布局 - Part2: 从内存布局角度看类型转换 static_cast, dynamic_cast, reinterpret_cast

0. 总论

开门见山,先把结论写在这里:

1)static_cast 在做基类指针和派生类指针之间的转换时,会根据编译时的静态偏移操作指针,但是没有运行期的类型安全检查,程序员需要自己确保类型的正确性,比如派生类指针确实指向了派生类对象。

2)dynamic_cast 依赖于虚表做运行期类型检查,适用于有虚函数的类型转换。

3)reinterpret_cast是最不安全的类型转换,完全暴力强制转换。

1. 没有虚函数的单继承

1.1 static_cast

由于是单继承,派生类对象的内存起始部分就是基类信息,而static_cast也不做运行期的类型检查,因此派生类对象指针和基类指针可以自由转换。

转换前后指针都指向派生类对象起始地址。

1.2 dynamic_cast

dynamic_cast需要做运行时类型检查,向上转换(派生类到基类)没有问题,因为派生类的内存布局中起始部分就是基类信息,但是对于向下转换(基类到派生类)来说,在没有虚函数(虚表)的情况下,无法关联typeinfo信息,没法保证一个基类指针指向的是不是派生类。

1.3 reinterpret_cast

暴力转换,转换前后指针都指向派生类对象起始地址。

1.4 三种转换的示例代码:

#include <iostream>

class Base {
public:
    int baseValue;
    Base(int v) : baseValue(v) {}
    void fool() {
        std::cout << "Base class fool()" << std::endl;
    }
};

class Derived : public Base {
public:
    int derivedValue;
    Derived(int b, int d) : Base(b), derivedValue(d) {}
    void fool() {
        std::cout << "Derived class fool()" << std::endl;
    }
};

int main() {
    Derived* dPtr = new Derived(20,30); // 创建 Derived 对象
    std::cout << "sizeof(Base): " << sizeof(Base) << std::endl;
    std::cout << "sizeof(Derived): " << sizeof(Derived) << std::endl;

    std::cout << "Derived Ptr: " << dPtr << std::endl;

    // 1. 使用 static_cast
    std::cout << "### Using static_cast:" << std::endl;
    Base* basePtr1 = static_cast<Base*>(dPtr); // Derived* 转换为 Base*
    std::cout << "static_cast basePtr1: " << basePtr1 << std::endl;
    basePtr1->fool(); // 调用 Base 的 fool()
    std::cout << "basePtr1->baseValue is " << basePtr1->baseValue << std::endl;

    // 从 Base* 转回 Derived*
    Derived* derivedPtr1 = static_cast<Derived*>(basePtr1); // Base* 转换为 Derived*
    std::cout << "static_cast derivedPtr1: " << derivedPtr1 << std::endl;
    derivedPtr1->fool(); // 调用 Derived 的 fool()
    std::cout << "derivedPtr1->baseValue is " << derivedPtr1->baseValue << std::endl;
    std::cout << "derivedPtr1->derivedValue is " << derivedPtr1->derivedValue << std::endl;

    // 2. 使用 dynamic_cast
    std::cout << "\n### Using dynamic_cast:" << std::endl;
    Base* basePtr2 = dynamic_cast<Base*>(dPtr); // Derived* 转换为 Base*
    std::cout << "dynamic_cast basePtr2: " << basePtr2 << std::endl;
    basePtr2->fool(); // 调用 Base 的 fool()
    std::cout << "basePtr2->baseValue is " << basePtr2->baseValue << std::endl;
    // 由于没有虚函数,dynamic_cast 无法正常使用,这行代码会导致编译错误
    //Derived* derivedPtr2 = dynamic_cast<Derived*>(basePtr2);

    // 3. 使用 reinterpret_cast
    std::cout << "\n### Using reinterpret_cast:" << std::endl;
    Base* basePtr3 = reinterpret_cast<Base*>(dPtr); // Derived* 转换为 Base*
    std::cout << "reinterpret_cast basePtr3: " << basePtr3 << std::endl;
    basePtr3->fool(); // 调用 Base 的 fool()
    std::cout << "basePtr3->baseValue is " << basePtr3->baseValue << std::endl;

    // 从 Base* 强制转换回 Derived*
    Derived* derivedPtr3 = reinterpret_cast<Derived*>(basePtr3); // 将 Base* 强制转换为 Derived*
    std::cout << "reinterpret_cast derivedPtr3: " << derivedPtr3 << std::endl;
    derivedPtr3->fool(); // 调用 Derived 的 fool()
    std::cout << "derivedPtr3->baseValue is " << derivedPtr3->baseValue << std::endl;
    std::cout << "derivedPtr3->derivedValue is " << derivedPtr3->derivedValue << std::endl;

    // 清理内存
    delete dPtr;

    return 0;
}

运行结果:

$ ./cast1
sizeof(Base): 4
sizeof(Derived): 8
Derived Ptr: 0x25a8eb0
### Using static_cast:
static_cast basePtr1: 0x25a8eb0
Base class fool()
basePtr1->baseValue is 20
static_cast derivedPtr1: 0x25a8eb0
Derived class fool()
derivedPtr1->baseValue is 20
derivedPtr1->derivedValue is 30

### Using dynamic_cast:
dynamic_cast basePtr2: 0x25a8eb0
Base class fool()
basePtr2->baseValue is 20

### Using reinterpret_cast:
reinterpret_cast basePtr3: 0x25a8eb0
Base class fool()
basePtr3->baseValue is 20
reinterpret_cast derivedPtr3: 0x25a8eb0
Derived class fool()
derivedPtr3->baseValue is 20
derivedPtr3->derivedValue is 30

1.5  内存布局图解

2. 有虚函数的单继承

2.1 示例代码

上面的代码稍作修改,将fool()变成虚函数,同时dynamic_cast向下转换可以生效:

#include <iostream>

class Base {
public:
    int baseValue;
    Base(int v) : baseValue(v) {}
    virtual void fool() {
        std::cout << "Base class fool()" << std::endl;
    }
};

class Derived : public Base {
public:
    int derivedValue;
    Derived(int b, int d) : Base(b), derivedValue(d) {}
    virtual void fool() override {
        std::cout << "Derived class fool()" << std::endl;
    }
};

int main() {
    Derived* dPtr = new Derived(20,30); // 创建 Derived 对象
    std::cout << "sizeof(Base): " << sizeof(Base) << std::endl;
    std::cout << "sizeof(Derived): " << sizeof(Derived) << std::endl;

    std::cout << "Derived Ptr: " << dPtr << std::endl;

    // 1. 使用 static_cast
    std::cout << "### Using static_cast:" << std::endl;
    Base* basePtr1 = static_cast<Base*>(dPtr); // Derived* 转换为 Base*
    std::cout << "static_cast basePtr1: " << basePtr1 << std::endl;
    basePtr1->fool(); // 调用 Base 的 fool()
    std::cout << "basePtr1->baseValue is " << basePtr1->baseValue << std::endl;

    // 从 Base* 转回 Derived*
    Derived* derivedPtr1 = static_cast<Derived*>(basePtr1); // Base* 转换为 Derived*
    std::cout << "static_cast derivedPtr1: " << derivedPtr1 << std::endl;
    derivedPtr1->fool(); // 调用 Derived 的 fool()
    std::cout << "derivedPtr1->baseValue is " << derivedPtr1->baseValue << std::endl;
    std::cout << "derivedPtr1->derivedValue is " << derivedPtr1->derivedValue << std::endl;

    // 2. 使用 dynamic_cast
    std::cout << "\n### Using dynamic_cast:" << std::endl;
    Base* basePtr2 = dynamic_cast<Base*>(dPtr); // Derived* 转换为 Base*
    std::cout << "dynamic_cast basePtr2: " << basePtr2 << std::endl;
    basePtr2->fool(); // 调用 Base 的 fool()
    std::cout << "basePtr2->baseValue is " << basePtr2->baseValue << std::endl;

    // 从 Base* 转回 Derived*
    Derived* derivedPtr2 = dynamic_cast<Derived*>(basePtr2);
    std::cout << "dynamic_cast derivedPtr2: " << derivedPtr2 << std::endl;
    derivedPtr2->fool(); // 调用 Derived 的 fool()
    std::cout << "derivedPtr2->baseValue is " << derivedPtr2->baseValue << std::endl;
    std::cout << "derivedPtr2->derivedValue is " << derivedPtr2->derivedValue << std::endl;

    // 3. 使用 reinterpret_cast
    std::cout << "\n### Using reinterpret_cast:" << std::endl;
    Base* basePtr3 = reinterpret_cast<Base*>(dPtr); // Derived* 转换为 Base*
    std::cout << "reinterpret_cast basePtr3: " << basePtr3 << std::endl;
    basePtr3->fool(); // 调用 Base 的 fool()
    std::cout << "basePtr3->baseValue is " << basePtr3->baseValue << std::endl;

    // 从 Base* 强制转换回 Derived*
    Derived* derivedPtr3 = reinterpret_cast<Derived*>(basePtr3); // 将 Base* 强制转换为 Derived*
    std::cout << "reinterpret_cast derivedPtr3: " << derivedPtr3 << std::endl;
    derivedPtr3->fool(); // 调用 Derived 的 fool()
    std::cout << "derivedPtr3->baseValue is " << derivedPtr3->baseValue << std::endl;
    std::cout << "derivedPtr3->derivedValue is " << derivedPtr3->derivedValue << std::endl;

    // 清理内存
    delete dPtr;

    return 0;
}

运行结果;

$ ./cast2
sizeof(Base): 16
sizeof(Derived): 16
Derived Ptr: 0x222ceb0
### Using static_cast:
static_cast basePtr1: 0x222ceb0
Derived class fool()
basePtr1->baseValue is 20
static_cast derivedPtr1: 0x222ceb0
Derived class fool()
derivedPtr1->baseValue is 20
derivedPtr1->derivedValue is 30

### Using dynamic_cast:
dynamic_cast basePtr2: 0x222ceb0
Derived class fool()
basePtr2->baseValue is 20
dynamic_cast derivedPtr2: 0x222ceb0
Derived class fool()
derivedPtr2->baseValue is 20
derivedPtr2->derivedValue is 30

### Using reinterpret_cast:
reinterpret_cast basePtr3: 0x222ceb0
Derived class fool()
basePtr3->baseValue is 20
reinterpret_cast derivedPtr3: 0x222ceb0
Derived class fool()
derivedPtr3->baseValue is 20
derivedPtr3->derivedValue is 30

2.2 内存布局图解

3. 没有虚函数的多继承

类图关系如下:

3.1 static_cast

可见,将Derived Ptr static_cast到Base2 Ptr以后,这个指针指向Derived instance内存布局当中Base2的部分起始地址。如果继续再做反方向的static_cast, 从 Base2 Ptr还可以回到Derived Ptr

3.2 dynamic_cast

没有虚函数的情况下,只允许派生类指针到基类指针的转换,基类指针不允许转为派生类指针。

3.3  reinterpret_cast

暴力转换,强制类型转换

此时,如果通过Base2 *b2ptr去访问Base2部分的内存内容会出问题,它并不像static_cast或者dynamic_cast那样做了指针偏移。

4. 有虚函数的多继承

Derived实例化以后的内存布局示意图:

4.1  static_cast

4.2 dynamic_cast 

4.3  reinterpret_cast

暴力转换,强制转换

此时,如果通过Base2 *b2ptr去访问Base2部分的内存内容会出问题,比如:

操作 b2ptr->baseValue2其实操作的是baseValue1

操作 b2ptr->foo2() 实际操作的是foo1()

  • 19
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值