请你解释一下这个有缺陷的例子:
Base base; Derived* d = reinterpret_cast<Derived*> (&base);
d->method();
d->virtual_method();
//output: Derived-method() Base-virtual_method()
我希望这段代码能够反过来行事。可能编译器为Base和Derived共享一个内存布局,当然vtable很常见。
所以我期待看到:
//output: Base-method() Derived-virtual_method()
答案 0 :(得分:5)
base
是Base
个对象;您将其字节重新解释为Derived
对象,然后尝试使用它,就像它是Derived
对象一样。执行此操作时的行为未定义。你的程序可能崩溃;看起来似乎做对了;它可能会让你的电脑点燃。
请注意,使用reinterpret_cast
来强制转换类层次结构永远不正确。您必须使用static_cast
或dynamic_cast
(或者,如果您要转换为基类,则不需要强制转换)。
解释为什么你会看到这种特殊行为:当你调用非虚拟成员函数时(就像你对d->method()
那样,假设method
是Derived
的非虚拟成员函数) ,被调用的函数是在编译时确定的,而不是在运行时确定的。
这里,编译器知道d
指向一个D
对象(因为你已经对编译器说谎并说它是),所以它会生成调用Derived::method()
的代码。根本没有“相对于指针的偏移”。不需要进行任何计算,因为在编译程序时,要调用的函数是已知的。
只有在调用虚拟成员函数时才需要进行表查找(即使这样,只有在编译器不知道调用成员函数的对象的动态类型时才需要查找。) / p>
当您致电d->virtual_method()
时,会调用Base::virtual_method
。为什么?在C ++的这个特定实现中,具有虚拟成员函数(多态类类型)的类类型的对象的前几个字节包含一个标记(称为“vptr”或“虚拟表”指针“),用于标识对象的实际类型。当您调用虚拟成员函数时,则在运行时检查该标记,并根据该标记选择被调用的函数。
当您将base
重新解释为Derived
对象时,实际上并未更改对象本身,因此其标记仍然表明它是Base
对象,因此为Base::virtual_method
1}}被调用。
但请记住,所有这些恰好恰好是使用特定版本的特定编译器编译此代码时发生的情况。行为未定义,这只是未定义行为可以表现出来的一种方式。
答案 1 :(得分:1)
编译器仅分配足够的内存来保存请求的对象。 Base可能是20个字节,Derived可能是额外的10个字节(因此Derived的大小为30个字节。)
当您为Base分配20个字节,然后(通过Derived)访问字节位置25时,它已超过分配的内存的末尾,并且(最多)您将崩溃。
编译器不能像你建议的那样为Base分配30个字节,因为这不仅会浪费,而且Derived可以在第三方库中实现,甚至可能不知道何时编译Base。