这是VC++ 2010中的错误吗?

Is this a bug in VC++ 2010?

本文关键字:错误 VC++ 2010 这是      更新时间:2023-10-16

我在C++试验钻石问题时遇到了这个问题。以下程序在Visual Studio 2010中为我工作,我得到了以下内容。

D
D
A
A
B
Error7
C
C
5
7
7
Press any key to continue . . .

如果你知道任何解释,请向我解释一下。

请注意,从不以任何方式构造任何 B 对象。大多数派生类 D 仅派生自 C 和 A。不是B。我想知道这m_i2从哪里获得价值 7?

#include "stdafx.h"
#include <iostream>
using namespace std;
class A{ int m_i1;
public:
    A(int i1){ m_i1 = i1;}  
    int GetMI1(){ return m_i1;} 
    void printABCD(){cout << "A" << endl;}
};
class B : public   A {
    int m_i2;
public:
    B(int i1):A(i1){} 
    void printABCD(){cout << "B" << endl;}
    void printEFGH(){cout << "Error" << m_i2 << endl;}
};
class C : virtual public A{
public:
    C(int i1):A(i1){}
    void printABCD(){cout << "C" << endl;}
};
class D : public C, public virtual A{
public:
    D(int i1):C(i1+1),A(i1){}
    void printABCD(){cout << "D" << endl;}
};
int _tmain(int argc, _TCHAR* argv[])
{
    A a(5); D d(7); C c(7); D* e = new D(7);
    d.printABCD();
    e->printABCD();
    ((A)d).printABCD();
    ((A*)e)->printABCD();
    ((B*)e)->printABCD();
    ((B*)e)->printEFGH();  // This line works perfectly, but why??
    //((B)(d)).printABCD(); //This will error and its right
    ((C*)e)->printABCD();
    ((C)d).printABCD();
    cout << a.GetMI1() << endl;
    cout << c.GetMI1() << endl;
    cout << d.GetMI1() << endl;
    system("pause");
    return 0;
}

Undefined behaviour is undefined. 当你取消引用(B*)e的那一刻,你的程序有未定义的行为(因为e实际上并没有指向B对象(,所以任何事情都可能发生。该程序可能看起来有效,可能会崩溃,可以在线订购披萨。

对这种行为的一种可能的解释可能是,D的布局恰好是这样的,C子对象首先在内存中,并且与int具有相同的大小(它可能只包含一个指向虚拟基A的指针(,然后是A子对象,因此其成员m_i1D对象开始的偏移量与成员m_i2 将具有B对象启动。因此,代码将该A::m_i1解释为执行B::printEFGH()B::m_i2。但这纯粹是猜测。与任何UB一样,实际上任何事情都可能发生。

编译器生成的代码完全按照你告诉它做的事情(你"告诉它"调用未定义的行为(。您调用的函数不是虚拟的,因此编译器可以(并且将(生成代码,只需将this推送到堆栈并调用成员函数,字面意思是不需要vtable解析的call

这是完全未定义的行为,我希望这是清楚的,所以不要这样做。你得到的值是内存中恰好与你传递的对象基数偏移量(不是 B 派生(的任何值。

如果你想看到一些奇怪的东西(同样是UB(。 添加一个int成员m_dvalclass D,然后在D构造上将其设置为42,然后UB调用是否完全按照您的要求。我认为报告为m_i2成员值的内容可能会让您感到惊讶(顺便说一句,这不是(。

首先,您的代码包含行为未定义的无效调用。

D类在其祖先中没有B类。这意味着从 D *B * 后的任何尝试使用生成的指针指向 B 类型的对象的任何强制转换都已无效。如果你使用C++式强制转换(static_cast(,编译器会告诉你它(就像它响应你的(B)(d)尝试一样(。尝试分析在此类强制转换后执行的任何调用是没有意义的。即这个

((B*)e)->printABCD();
((B*)e)->printEFGH();

具有未定义的行为。

其次,你说你"在C++试验钻石问题"。代码中唯一与"钻石问题"有一定关系的部分实际上是这个调用

cout << d.GetMI1() << endl;

它显示D(7) -> A(7)初始值设定项胜过D(7) -> C(8) -> A(8)初始值设定项。其余的调用与任何"钻石"效果完全无关。