无效 C 风格强制转换后的方法调用

Method call after invalid C-style cast works

所以我想更多地了解 C-style-casts 之间的区别static_cast, dynamic_cast 我决定试试这个例子,它应该反映 C-style-castsstatic_cast 之间的差异还不错

class B
{
public:
    void hi() { cout << "hello" << endl; }
};

class D: public B {};

class A {};

int main()
{
    A* a = new A();
    B* b = (B*)a;
    b->hi();
}

好吧,这段代码片段应该反映出 C 风格转换 非常错误,根本没有检测到错误转换.部分情况是这样发生的。未检测到错误转换,但令我惊讶的是程序并没有在 b->hi(); 处崩溃,而是在屏幕上打印了 "hello".

这个词

现在,为什么会这样?当没有实例化 B 对象时,使用什么对象调用这样的方法?我正在使用 g++ 进行编译。

Now, why is this happening ?

因为它可能发生。任何事情都可能发生。行为是 undefined.

意外发生的事实很好地说明了为什么 UB 如此危险。如果它总是导致崩溃,那么处理起来就容易多了。

What object was used to call such a method

很可能,编译器盲目地信任你,并假定 b 确实指向一个 B 类型的对象(但它没有)。它可能会使用指向的内存,就好像假设是真的一样。成员函数不访问属于该对象的任何内存,其行为恰好与存在正确类型的对象一样。

由于未定义,行为可能完全不同。如果您再次尝试 运行 该程序,标准不保证恶魔不会从您的鼻子里飞出来。

正如其他人所说,这是未定义的行为。

为什么它起作用了?这可能是因为函数调用是在编译时静态链接的(它不是虚函数)。函数 B::hi() 存在,因此被调用。尝试将变量添加到 class B 并在函数 hi() 中使用它。然后你会在屏幕上看到问题(垃圾值):

class B
{
public:
    void hi() { cout << "hello, my value is " << x << endl; }

private:
    int x = 5;
};

否则,您可以将函数 hi() 设为虚拟。然后函数在运行时动态链接,程序立即崩溃:

class B
{
public:
    virtual void hi() { cout << "hello" << endl; }
};

这仅是因为 hi() 方法本身的实现,以及 C++ 规范中称为 未定义行为.

的特殊部分

使用 C 样式强制转换为不兼容的指针类型是未定义的行为 - 几乎任何事情都可能发生。

在这种情况下,编译器显然已经决定只信任你,并决定相信 b 确实是指向 B 实例的有效指针——事实上这就是全部C 风格的强制转换将永远如此,因为它们不涉及 运行 时间行为。当您对其调用 hi() 时,该方法有效,因为:

  • 它不访问属于 B 但不属于 A 的任何实例变量(事实上,它根本不访问任何实例变量)
  • 它不是虚拟的,所以不需要在 b 的 vtable 中查找它来调用

因此它有效,但在几乎所有重要的情况下,这种格式错误的强制转换后跟方法调用都会导致崩溃或内存损坏。而且您不能依赖这种行为 - undefined 也不意味着每次 运行 它都必须相同。编译器完全有权使用此代码插入随机数生成器,并在生成 1 时启动原始 Doom 的完整副本。每当涉及未定义行为的任​​何事情似乎起作用时,请牢记这一点,因为它明天可能不起作用,您需要这样对待它。