GMa*_*ckG 115 c++ standards-compliance null-pointer undefined-behavior language-lawyer
请考虑以下代码:
#include <iostream>
struct foo
{
// (a):
void bar() { std::cout << "gman was here" << std::endl; }
// (b):
void baz() { x = 5; }
int x;
};
int main()
{
foo* f = 0;
f->bar(); // (a)
f->baz(); // (b)
}
Run Code Online (Sandbox Code Playgroud)
我们期望(b)崩溃,因为x空指针没有相应的成员.在实践中,(a)不会崩溃,因为this从不使用指针.
因为(b)取消引用this指针((*this).x = 5;),并且this为null,程序进入未定义的行为,因为取消引用null总是被称为未定义的行为.
会(a)导致未定义的行为吗?如果两个函数(和x)都是静态的呢?
GMa*_*ckG 112
双方(a)并(b)导致不确定的行为.通过空指针调用成员函数始终是未定义的行为.如果该函数是静态的,那么它在技术上也是未定义的,但存在一些争议.
首先要理解的是为什么取消引用空指针是未定义的行为.在C++ 03中,这里实际上有点含糊不清.
虽然在§1.9/ 4和§8.3.2/ 4的注释中提到了"解除引用空指针导致未定义的行为",但它从未明确说明.(注释是非规范性的.)
但是,可以尝试从§3.10/ 2推导出它:
左值是指对象或函数.
解除引用时,结果是左值.空指针不引用对象,因此当我们使用左值时,我们有未定义的行为.问题是前一句从未说过,所以"使用"左值是什么意思?甚至可以生成它,或者在更正式的执行左值到右值转换的意义上使用它?
无论如何,它绝对不能转换为右值(§4.1/ 1):
如果左值引用的对象不是类型T的对象,并且不是从T派生的类型的对象,或者如果对象未初始化,则需要此转换的程序具有未定义的行为.
这绝对是未定义的行为.
歧义来自于它是否是未定义的行为,而不是使用无效指针中的值(即获取左值而不是将其转换为右值).如果没有,那么int *i = 0; *i; &(*i);定义明确.这是一个活跃的问题.
所以我们有一个严格的"取消引用空指针,获取未定义的行为"视图和弱"使用解除引用的空指针,获取未定义的行为"视图.
现在我们考虑这个问题.
是的,(a)导致未定义的行为.实际上,如果this为null,则无论函数的内容如何,结果都是未定义的.
这遵循§5.2.5/ 3:
如果
E1类型为"指向类X的指针",则表达式E1->E2将转换为等效形式(*(E1)).E2;
*(E1)将导致具有严格解释的未定义行为,.E2并将其转换为右值,使其成为弱解释的未定义行为.
它也遵循直接来自(§9.3.1/ 1)的未定义行为:
如果为非X类型的对象或从X派生的类型调用类X的非静态成员函数,则行为未定义.
对于静态函数,严格与弱的解释会产生差异.严格来说,它是未定义的:
可以使用类成员访问语法来引用静态成员,在这种情况下,评估对象表达式.
也就是说,它被评估为非静态的,我们再一次取消引用空指针(*(E1)).E2.
但是,因为E1没有在静态成员函数调用中使用,如果我们使用弱解释,则调用是明确定义的.*(E1)导致左值,静态函数被解析,*(E1)被丢弃,函数被调用.没有左值到右值的转换,因此没有未定义的行为.
在C++ 0x中,从n3126开始,模糊性仍然存在.现在,安全:使用严格的解释.
Mar*_*som 29
显然未定义意味着它没有被定义,但有时它可以是可预测的.我要提供的信息永远不应该依赖于工作代码,因为它当然不能保证,但在调试时可能会有用.
您可能认为在对象指针上调用函数将取消引用指针并导致UB.在如果函数不是虚拟实践,编译器将其转换为纯函数调用传递指针作为第一个参数这个,绕过解引用和用于被叫的成员函数创建定时炸弹.如果成员函数没有引用任何成员变量或虚函数,它实际上可能成功而没有错误.请记住,成功属于"未定义"的宇宙!
微软的MFC功能GetSafeHwnd实际上依赖于这种行为.我不知道他们在吸烟.
如果你正在调用一个虚函数,必须取消引用指针才能到达vtable,并且肯定你会得到UB(可能是崩溃但记住没有保证).
| 归档时间: |
|
| 查看次数: |
14129 次 |
| 最近记录: |