【问题标题】:Why does reinterpret_cast work in some cases but not others?为什么 reinterpret_cast 在某些情况下有效,而在其他情况下无效?
【发布时间】:2019-12-27 01:18:10
【问题描述】:

我刚开始与一个使用reinterpret_cast 的团队合作,显然它应该是dynamic_cast。尽管他们使用的是reinterpret_cast,但代码似乎仍然可以正常工作,所以我决定不理会它,直到最近它最终停止工作。

struct Base {
 virtual void do_work() = 0;
};
struct D1 : public Base {
 virtual void do_work();
 std::vector<int> i;
};

struct D2: public D1 { 
 void do_work()
};

struct Holds_data {
    std::vector<int> i;
};
struct Use_data : public Holds_data {
 virtual void do_work();
};


struct A : public Use_data, public Base {
    void do_work();
};

//case 1
// this code works
Base* working = new D2();
D2*   d2_inst = reinterpret_cast<D2*>(working);


//case 2
Base* fail = new A();
A*    A_inst  = reinterpret_cast<A*>(fail); // fails
A*    A_inst  = dynamic_cast<A*>(fail);     // works

在情况 1 中似乎没有问题,重新解释演员表似乎可以正常工作。 在案例 2 中,我注意到 std::vector 的内部数据在使用 reinterpret cast 时似乎已损坏

我的问题是为什么案例 1 通过了? std::vector 中不应该有数据损坏吗?

【问题讨论】:

  • 未定义的行为可以做任何事情。这意味着有时它会做你期望的事情,有时它会做一些疯狂的事情。当 UB 按您的预期工作时,这是一个不幸的巧合。
  • 显然不同的是在非工作情况下使用多重继承。但是正如已经说过的,两者都是错误的,但是当所有标准都说是未定义的行为时,您期望错误是不正确的。
  • 重命名第二个A_inst,然后打印出failA_inst和另一个A_inst 的地址。你会看到一些神奇的东西。 reinterpret_cast 是对地址的字面重新解释。 dynamic_casts 做一些不同的事情,更聪明。
  • reinterpret_cast(甚至是 C 风格的演员表)有它们的位置,并且有时(虽然很少)是正确的做法。 大多数时候他们是bugs - 就像在这种情况下。另请参阅:en.cppreference.com/w/cpp/language/ubblog.llvm.org/2011/05/what-every-c-programmer-should-know.htmlblog.regehr.org/archives/213

标签: c++ dynamic-cast reinterpret-cast


【解决方案1】:

简答

问题是Base* working = new D2(); 隐式地将D2* 转换为Base* (static_cast)。

如果你有:

D2* d2 = new D2();
Base* b = d2;

您无法确定std::addressof(d2) == std::addressof(b) 是否为真。但是reinterpret_cast 只有在std::addressof(d2) == std::addressof(b) 为真时才有效。让你的代码正常运行就好像是在cmets中提到的只是一个幸运的巧合。


更详细

D2 类的内存布局可能如下所示:

class D2:
0x0000 Attributes of Base
...
0x0010 Attirbutes of D1
...
0x0020 Attributes of D2
...

Base* b = new D2() 将保存Base (0x0000) 的地址。由于基类的属性总是存储在子类的属性之前,所以b(0x0000)中存储的地址与new D2()(0x0000)和reinterpret_cast返回的地址相同。

但另一方面,A 类的内存布局可能如下所示:

class A:
0x0000 Attributes of HoldData
...
0x0010 Attributes of UserData
...
0x0020 Attributes of Base
...
0x0030 Attributes of A
...

这里编译器必须首先存储UserDataBase 的数据。因此,如果首先存储UserData(如示例中所示),Base* b = new A() 还将保存Base 的地址(0x0020),但由于Base 不是A 中的第一个存储类,因此该地址new A() (0x0000) 返回的地址不等于b (0x0020) 中保存的地址,因为new A() (0x0000) 被隐式静态转换为Base*。这意味着reinterpret_cast 将在此处失败。

这就是 case1 有效而 case2 无效的原因。


最后一件事:你永远不应该相信编译器总是使用相同的内存布局。关于内存布局有很多东西在标准中没有定义。 使用reinterpret_casthere 是未定义的行为!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-04-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-22
    • 1970-01-01
    相关资源
    最近更新 更多