reinterpret_cast的奇怪行为

时间:2013-12-27 05:24:45

标签: 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 }

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

但是上面的代码实际上运行良好,它会输出“1”,即enum TWO。

也许我的简化有一些问题,但你认为使用reinterpret_case上述代码有可能出现问题吗?

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

3 个答案:

答案 0 :(得分:1)

您看到了这一点,因为B中包含的D不存在于同一地址。这可能与虚拟方法调度表的实现有关。该语言没有这样的保证,因为BD都不是POD类型。

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

  

你触及了我正在寻找的东西,关于调度表,但它不是那么清楚,你能详细说明吗?

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;
};

此外,编译器自动生成一些代码和您的虚拟方法(即使您以这种方式编写它也可能无法内联,因为需要指向它的指针才能使虚拟表工作): / p>

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. D虚拟表中的第一件事是B的虚拟表。
  2. BD中的第一件事是指向对象类的虚拟表的指针。
  3. 恰好发生了 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将会像您预期的那样失败。

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

答案 1 :(得分:0)

reinterpret_cast无法正确处理类型调度。如果您已经知道要使用的类型,请使用static_cast。如果您不知道要使用的类型,请使用dynamic_cast并确保dynamic_cast返回的指针有效。

答案 2 :(得分:0)

在单继承层次结构中,当层次结构的最顶级类已经是多态的时,层次结构中的所有转换都执行相同的操作:无。它们只是“概念上”将指针值重新解释为不同类型的值。当用于向下转发时,只有dynamic_cast会执行一些额外的检查。

出于这个原因,reinterpret_cast恰好为此工作“工作”。鉴于你的课程定义,没有办法强迫它不起作用。