解释bug行为

Explain bug behaviour

本文关键字:行为 bug 解释      更新时间:2023-10-16

你能解释一下这个有bug的例子是怎么回事吗?

Base base; Derived* d = reinterpret_cast<Derived*> (&base);
d->method();
d->virtual_method();
//output: Derived-method() Base-virtual_method() 

我希望这段代码以相反的方式运行。编译器可能为基类和派生类共享一个内存布局,当然虚函数表也是通用的。

  • 当d->方法被调用时,我希望编译器说"我只是在相对于我的指针的偏移0处调用方法。"指针指向基对象,这是周围唯一的对象。
  • 当d->virtual_method被调用时,编译器应该说我将通过虚函数表解析它,因此应该调用派生方法(尽管Base对象是唯一的一个,布局扩展到派生)。

所以我期待看到:

//output: Base-method() Derived-virtual_method()

baseBase对象;您将其字节重新解释为Derived对象,然后尝试像使用Derived对象一样使用它。这样做时的行为是未定义的。你的程序可能会崩溃;它可能看起来是在做正确的事情;这可能会使你的电脑着火。

注意,使用reinterpret_cast来上下转换类层次结构是永远不正确的。必须使用static_castdynamic_cast(或者,如果要转换为基类,则可能不需要强制转换)。


解释一下为什么会出现这种特殊的行为:当调用非虚成员函数时(就像使用d->method()一样,假设methodDerived的非虚成员函数),调用的函数是在编译时确定的,而不是在运行时确定的。

在这里,编译器知道d指向一个D对象(因为你对编译器撒谎说它是),所以它生成调用Derived::method()的代码。根本没有"相对于指针的偏移量"。不需要进行计算,因为要调用的函数在编译程序时是已知的。

只有在调用虚成员函数时才需要进行表查找(即使这样,只有在编译器不知道调用成员函数的对象的动态类型时才需要进行查找)。

当你调用d->virtual_method()时,Base::virtual_method会被调用。为什么?在c++的这个特殊实现中,具有虚成员函数(多态类类型)的类类型的对象的前几个字节包含一个标记(称为"vptr"或"虚表指针"),该标记标识对象的实际类型。当您调用虚拟成员函数时,在运行时将检查该标记,并根据该标记选择被调用的函数。

当您将base重新解释为Derived对象时,实际上并没有改变对象本身,因此它的标记仍然声明它是Base对象,这就是为什么调用Base::virtual_method

但是,请记住,所有这些都是在使用特定编译器的特定版本编译此代码时发生的情况。该行为是未定义的,这只是未定义行为可以显示自己的一种方式。

编译器只分配足够的内存来保存请求的对象。Base可能是20字节,Derived可能是在此基础上额外的10字节(因此Derived的大小为30字节)

当你为Base分配了20个字节,然后(通过派生)访问字节位置25,它已经超过了分配的内存的末端,(最多)你会得到一个崩溃。

编译器不能像你建议的那样为Base分配30个字节,因为这不仅会浪费,而且派生函数可以在第三方库中实现,甚至可能不知道Base何时被编译。