c++ - reinterpret_cast 的奇怪行为

标签 c++

我刚刚在使用reinterpret_cast的代码中发现了一个错误,将其更改为dynamic_cast后,问题就消失了。然后,我尝试使用以下代码重现该行为:

  1 #include <iostream>
  2
  3 using namespace std;
  4
  5 typedef enum {
  6     ONE, TWO,
  7 } Num;
  8
  9 class B {
 10     public:
 11         virtual Num f() const = 0;
 12 };
 13
 14 class D: public B {
 15     public:
 16         virtual Num f() const { return TWO; }
 17 };
 18
 19 int main()
 20 {
 21     B *b = new D();
 22     cout << "f()=" << reinterpret_cast<D*>(b)->f() << endl << endl;
 23
 24     return 0;
 25 }

这段代码是我刚刚修复的错误的简化版本,基本上我尝试重新生成错误,这样如果我不将 reinterpret_cast 替换为 dynamic_cast,则会在第 22 行返回一个野生枚举数;在我将其更改为 dynamic_cast 之后,返回了正确的枚举。

但上面的代码实际上运行良好,它确实打印出“1”,这是枚举二。

也许我的简化有一些问题,但您是否看到上述代码在使用 reinterpret_case 时可能有问题?

是的,我知道使用 reinterpret_case 没有意义,只是我想知道发生了什么。

最佳答案

你看到这个是因为 B包含在 D 中不存在于同一地址。这可能与虚方法调度表的实现有关。该语言不提供此类保证,因为 B也不D是 POD 类型。

reinterpret_cast基本上是告诉编译器“采用这个值的位模式并将其视为其他类型,而不更改它。”

You touched [on] something which is what I am looking for, about the dispatch table, but it is not so clear, can you elaborate?

C++ 不规定编译器和/或 ABI 实现的细节。因此不能保证 reinterpret_castdynamic_cast会做同样的事情。您偶然发现了一种情况(如 AndreyT 所指出的那样,是单继承层次结构),它在您的编译器上确实如此。

当你声明一个类有virtual成员,它不再是存储对象的内容与类声明中所写完全相同的 POD 类型,因为编译器在存储开始处添加了一个隐藏的“虚拟表指针”。该指针指向包含该类虚成员详细信息的每个类表,例如指向该类虚方法的函数指针。例如,您写道:

class B {
    public:
        virtual Num f() const = 0;
};

但是为 B 存储了什么?大概是这样的:

struct __VirtualsForB {
    Num (*f)(const B* this);
};

struct B {
    const __VirtualsForB* const __vtbl;
};

然后你写:

class D: public B {
    public:
        // BTW, don't need to say `virtual` here. Virtual-ness is inherited.
        virtual Num f() const { return TWO; }
};

的存储方式如下:

struct __VirtualsForD {
    __VirtualsForB __super;
};

// This exists since D is not abstract.
extern const __VirtualsForD __virtuals_for_D;

struct D {
    const __VirtualsForD* const __vtbl;
};

另外,编译器自动生成一些代码和你的虚拟方法(即使你这样写它也可能无法内联,因为虚拟表需要一个指向它的指针才能工作):

Num __D__f(const B* __in_this)
{
    const D* this = static_cast<const D*>(__in_this);
    return TWO;
}

const __VirtualsForD __virtuals_for_D = { __D__f } ;

然后当你写:

B *b = new D();

变成了这样的东西:

// Allocate a D.
D* _new_D = (D*)operator new(sizeof(D));
// Construct the D.
_new_D.__vtbl = __virtuals_for_D;
B *b = static_cast<B*>(_new_D);

并且由于这个实现的一些巧合事实:

  1. 虚拟表中的第一件事为DB 的虚拟表.
  2. B 中的第一件事或 D是指向对象类的虚拟表的指针。

恰好 reinterpret_castdynamic_cast做同样的事情(即什么都不做)和你的 cout << reinterpret_cast<D*>(b)->f()成功。顺便说一句,它变成了这样的东西:

B* __temp = static_cast<B*>(reinterpret_cast<D*>(b));
Num __temp2 = (*__temp.__vtbl.f)(__temp);
std::ostream::operator<<(cout, __temp2);
// ...

如果这些条件中的任何一个不成立,就像多重继承或虚拟基类继承中经常出现的情况,那么 reinterpret_cast会像您预期的那样失败。

这实际上是实现定义的行为。

关于c++ - reinterpret_cast 的奇怪行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20795275/

相关文章:

c++ - 为什么这不编译?

c++ - OpenCV 错误 : Assertion failed (a_size. 宽度 == 长度)

c++ - C++ 中的模板 lambda 表达式

c++ - 将 lambda 表达式传递给 lambda 参数 c++11

c++ - 如何将 const 成员函数作为非常量成员函数传递

c++ - 如何使用命名空间和类?

c++ - 确保 move 构造函数存在时永远不会调用复制构造函数

c++ - 从管道 block 和死锁中读取

模仿 matlab 功能的 C++ 库

c++ - Qt 信号和槽线程安全