这个 C++ 程序会调用未定义的行为吗?

Does this C++ program invoke undefined behavior?

我正在阅读有关 static_cast 运算符的内容。

考虑以下示例:

#include <iostream>
class B { };
class D : public B 
{
    public:
        void fun()
        {
            std::cout<<"fun() is called\n";
        }
};
void f(B* pb,D* pd)
{
    D* pd2=static_cast<D*>(pb);
    B* pb2=static_cast<B*>(pd);
    pd2->fun();
}
int main()
{
    B b;
    D d;
    f(&b,&d);
}

上面写着:

In the example that follows, the line D* pd2 = static_cast(pb); is not safe because D can have fields and methods that are not in B. However, the line B* pb2 = static_cast(pd); is a safe conversion because D always contains all of B.

In contrast to dynamic_cast, no run-time check is made on the static_cast conversion of pb. The object pointed to by pb may not be an object of type D, in which case the use of *pd2 could be disastrous. For instance, calling a function that is a member of the D class, but not the B class, could result in an access violation.

我在 gcc 4.8.1 和 MSVS 2010 上试过并得到输出 调用了 fun()。那么这个程序会调用未定义的行为吗?我的程序会在运行时崩溃吗? C++ 标准对此有何规定?如果我理解有误,请纠正我。

是的,当然是。

您正在通过强制从 B*D* 的转换在仅是 B 的对象上调用 D 的成员函数。

它 "appears to work" 因为所涉及的函数不会尝试访问任何实际数据,因此,您的计算机不会在运行时注意到和抱怨内存访问。


事实上,我们甚至不需要费心去讨论函数调用是否有未定义的行为;演员表本身已经够糟糕了:

[C++14: 5.4.9/11]: A prvalue of type “pointer to cv1 B,” where B is a class type, can be converted to a prvalue of type “pointer to cv2 D,” where D is a class derived (Clause 10) from B, if a valid standard conversion from “pointer to D” to “pointer to B” exists (4.10), cv2 is the same cv-qualification as, or greater cv-qualification than, cv1, and B is neither a virtual base class of D nor a base class of a virtual base class of D. The null pointer value (4.10) is converted to the null pointer value of the destination type. If the prvalue of type “pointer to cv1 B” points to a B that is actually a subobject of an object of type D, the resulting pointer points to the enclosing object of type D. Otherwise, the behavior is undefined.

当您使用 static_castB* 向上转换为 D* 时,编译器相信您已经检查过这是正确的做法并且指针确实指向 D。编译器或运行时无法代表您检查它(没有 RTTI)。因此,当您调用 foo 时,它会愉快地放入调用它的代码,并且因为它不是虚拟方法,所以会调用正确的 D::foo 。如果您访问 foo 中的任何 D 特定成员,应用程序将崩溃、烧毁等。这就是现实。

理论是,您当然会调用未定义的行为。