解释bug行为
Explain bug behaviour
你能解释一下这个有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()
base
是Base
对象;您将其字节重新解释为Derived
对象,然后尝试像使用Derived
对象一样使用它。这样做时的行为是未定义的。你的程序可能会崩溃;它可能看起来是在做正确的事情;这可能会使你的电脑着火。
注意,使用reinterpret_cast
来上下转换类层次结构是永远不正确的。必须使用static_cast
或dynamic_cast
(或者,如果要转换为基类,则可能不需要强制转换)。
解释一下为什么会出现这种特殊的行为:当调用非虚成员函数时(就像使用d->method()
一样,假设method
是Derived
的非虚成员函数),调用的函数是在编译时确定的,而不是在运行时确定的。
在这里,编译器知道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何时被编译。
- 理解boost::asio-async_read在无需读取内容时的行为
- 模板-模板参数推导:三个不同的编译器三种不同的行为
- arr[-1]在c++中的奇怪行为
- 继承期间显示未知行为的子类
- 如何在c++中使用引用实现类似python的行为
- G锁定铸造到基础上会释放模拟行为
- 在C++中对T*类型执行std::move的意外行为
- Clang bug?使用指针作为模板参数
- std::当在256字节边界上写入整数时,流的奇怪行为
- 不知道某个东西是否被忽略会引入未定义的行为吗
- 奇怪的构造函数行为
- 重载运算符new[]的行为取决于析构函数
- 不同语言中相同代码的不同行为
- 处理除以零会导致<csignal>意外行为
- 试图理解类对象的行为
- c++11评估顺序(未定义的行为)
- 从结构寻址时,MMAP变量的行为很奇怪
- 我可以做些什么来消除或最小化这种将提供相同功能和行为的代码重复
- for循环中的奇怪行为-一个bug
- 解释bug行为