C++ 在空实例上调用成员函数何时会导致未定义的行为?

C++ 在空实例上调用成员函数何时会导致未定义的行为?,c++,undefined-behavior,language-lawyer,standards-compliance,null-pointer,C++,Undefined Behavior,Language Lawyer,Standards Compliance,Null Pointer,考虑以下代码: #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-&

考虑以下代码:

#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)
}
#包括
结构foo
{
//(a):
void bar(){std::cout baz();/(b)
}
我们预计
(b)
会崩溃,因为空指针没有相应的
x
成员。实际上,
(a)
不会崩溃,因为从未使用
指针

由于
(b)
取消引用
这个
指针(
(*this).x=5;
),并且
这个
为空,程序进入未定义的行为,因为取消引用空值总是被称为未定义的行为

(a)
是否会导致未定义的行为?如果两个函数(和
x
)都是静态的呢?

两个
(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)
会导致未定义的行为。事实上,如果
为空,则无论函数的内容如何,结果都是未定义的

这源自§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开始,模糊性仍然存在。现在,请注意:使用严格的解释。

显然未定义意味着它未定义,但有时它是可预测的。我将提供的信息永远不应该被用于工作代码,因为它肯定不能保证,但它可能会有用调试

您可能认为在对象指针上调用函数会取消对指针的引用并导致UB。实际上,如果函数不是虚拟函数,编译器会将其转换为普通函数调用,将指针作为第一个参数传递,从而绕过取消引用并为被调用的成员函数创建定时炸弹。如果成员函数不引用任何成员变量或虚拟函数,它实际上可能会成功而不会出错。请记住,成功属于“未定义”的范围

微软的MFC功能实际上依赖于这种行为。我不知道他们在抽什么


如果要调用虚拟函数,必须取消对指针的引用,才能访问vtable,而且您肯定会得到UB(可能是崩溃,但请记住没有保证)。

+1.继续迂腐,在“弱定义”下,非静态成员函数没有被“为非X类型的对象”调用。它被要求提供一个完全不是对象的左值。因此,建议的解决方案在你引用的条款中添加了文本“或者如果左值是空左值”。你能澄清一点吗?特别是你的“已解决问题”和“活动问题”链接,问题编号是什么?另外,如果这是一个已结束的问题,静态函数的是/否答案是什么?我觉得我缺少了