无效 C 风格强制转换后的方法调用
Method call after invalid C-style cast works
所以我想更多地了解 C-style-casts、 之间的区别static_cast, dynamic_cast 我决定试试这个例子,它应该反映 C-style-casts 和 static_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 的完整副本。每当涉及未定义行为的任何事情似乎起作用时,请牢记这一点,因为它明天可能不起作用,您需要这样对待它。
所以我想更多地了解 C-style-casts、 之间的区别static_cast, dynamic_cast 我决定试试这个例子,它应该反映 C-style-casts 和 static_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 的完整副本。每当涉及未定义行为的任何事情似乎起作用时,请牢记这一点,因为它明天可能不起作用,您需要这样对待它。