为什么在使用静态方法时取消引用 nullptr 而不是 C++ 中的未定义行为?

Ant*_*hov 9 c++ static-methods null-pointer undefined-behavior language-lawyer

我正在阅读一篇关于nullptrC++ 中一些特性的文章,一个特定的例子在我的理解中引起了一些混乱。

考虑(来自上述帖子的简化示例):

struct A {   
    void non_static_mem_fn() {}  
    static void static_mem_fn() {}  
};


A* p{nullptr};

/*1*/ *p;
/*6*/ p->non_static_mem_fn();
/*7*/ p->static_mem_fn();
Run Code Online (Sandbox Code Playgroud)

根据作者的说法/*1*/,取消引用 the 的表达式nullptr本身不会导致未定义的行为。与/*7*/使用nullptr-object 调用静态函数的表达式相同。

理由基于C++ Standard Core Language Closed Issues, Revision 100中的issue 315

... *pis not an error when pis null 除非左值被转换为右值(7.1 [conv.lval]),它不在这里。

从而区分/*6*//*7*/

因此,对实际的间接引用nullptr 不是未定义行为在SO答案在C ++标准的问题232讨论,...)。因此,/*1*/在这个假设下, 的有效性是可以理解的。

但是,如何/*7*/保证不引起UB呢?根据引用的引用,在 中没有从左值到右值的转换p->static_mem_fn();。但同样适用于/*6*/ p->non_static_mem_fn();,我认为我的猜测得到了同一期 315 中关于以下内容的引用:

/*6*/在 12.2.2 [class.mfct.non-static] 中明确指出为未定义,尽管有人可能会争辩说,因为它non_static_mem_fn();是空的,所以没有左值-> 右值转换。

(在引用中,我更改了“which”并f()获得与此问题中使用的符号的联系)。


那么,为什么这样的区分做出了p->static_mem_fn();p->non_static_mem_fn();关于UB的因果关系?是否有意使用从可能是的指针调用静态函数nullptr


附录:

cdh*_*wie 6

此答案中的标准引用来自 C++17 规范 (N4713)。

您的问题中引用的部分之一回答了非静态成员函数的问题。[class.mfct.非静态]/2:

如果为不是 类型或派生类型的X对象调用类的非静态成员函数,则行为未定义。XX

例如,这适用于通过不同的指针类型访问对象:

std::string foo;

A *ptr = reinterpret_cast<A *>(&foo); // not UB by itself
ptr->non_static_mem_fn();             // UB by [class.mfct.non-static]/2
Run Code Online (Sandbox Code Playgroud)

空指针不指向任何有效对象,因此它当然也不指向该类型的对象A。使用你自己的例子:

p->non_static_mem_fn(); // UB by [class.mfct.non-static]/2
Run Code Online (Sandbox Code Playgroud)

既然如此,为什么这在静态情况下有效呢?让我们将标准的两个部分放在一起:

[expr.ref]/2:

...表达式E1->E2转换为等效形式(*(E1)).E2...

[class.static]/1(强调我的):

...静态成员可以使用类成员访问语法来引用,在这种情况下将评估对象表达式。

特别是第二个块表示即使对于静态成员访问也会评估对象表达式。例如,如果它是具有副作用的函数调用,则这一点很重要。

放在一起,这意味着这两个块是等效的:

// 1
p->static_mem_fn();

// 2
*p;
A::static_mem_fn();
Run Code Online (Sandbox Code Playgroud)

因此,要回答的最后一个问题是当为空指针值时是否*p 单独存在未定义行为。p

传统观点会说“是”,但事实并非如此。 标准中没有任何内容表明单独取消引用空指针是 UB,并且有几个直接支持这一点的讨论:

  • 正如您在问题中提到的,问题 315*p明确指出当结果未使用时不是 UB。
  • DR 1102删除了“取消引用空指针”作为 UB 的示例。给出的理由是:

    围绕取消引用空指针的未定义行为存在一些核心问题。其意图似乎是取消引用明确定义,但使用取消引用的结果将产生未定义的行为。这个主题太混乱了,不能作为未定义行为的参考示例,或者如果要保留它,应该更准确地说明。

  • 此 DR 链接到问题 232,其中讨论添加措辞,明确指示*p为 空指针时的定义行为p(只要不使用结果)。

综上所述:

p->non_static_mem_fn(); // UB by [class.mfct.non-static]/2
p->static_mem_fn();     // Defined behavior per issue 232 and 315.
Run Code Online (Sandbox Code Playgroud)