本文介绍了为什么在使用静态方法时取消引用 nullptr 而不是 C++ 中的未定义行为?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在阅读 一篇关于 C++ 中一些 nullptr 特性的帖子,一个特定的例子在我的理解中引起了一些混乱.

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

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();

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

理由基于 issue 315 in C++标准核心语言已关闭问题,修订版 100 具有

...*pp 为空时不是错误,除非左值被转换为右值 (7.1 [conv.lval]),它不是不在这里.

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

因此,nullptr 的实际取消引用并不是未定义的行为(对 SO 的回答在问题 232 下的讨论C++ 标准,...).因此,在这个假设下 /*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() 以获得与此问题中使用的符号的连接).


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


附录:

解决方案

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

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

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

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

std::string foo;A *ptr = reinterpret_cast(&foo);//不是 UB 本身ptr->non_static_mem_fn();//UB by [class.mfct.non-static]/2

空指针不指向任何有效对象,所以它当然也不指向A类型的对象.使用您自己的示例:

p->non_static_mem_fn();//UB by [class.mfct.non-static]/2

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

[expr.ref]/2:

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

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

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

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

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

//1p->static_mem_fn();//2*p;A::static_mem_fn();

所以最后要回答的问题是 *p alonep 是空指针值时是否是未定义的行为.

传统智慧会说是";但事实并非如此.标准中没有任何规定单独取消引用空指针是 UB 并且有几个讨论直接支持这一点:

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

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

  • 此 DR 链接到 issue 232p 为空指针时,添加明确表示*p 为定义行为的措辞,只要不使用结果.

总结:

p->non_static_mem_fn();//UB by [class.mfct.non-static]/2p->static_mem_fn();//每个问题 232 和 315 定义的行为.

I was reading a post on some nullptr peculiarities in C++, and a particular example caused some confusion in my understanding.

Consider (simplified example from the aforementioned post):

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();

According to the authors, expression /*1*/ that dereferences the nullptr does not cause undefined behaviour by itself. Same with expression /*7*/ that uses the nullptr-object to call a static function.

The justification is based on issue 315 in C++ Standard Core Language Closed Issues, Revision 100 that has

thus making a distinction between /*6*/ and /*7*/.

So, the actual dereferencing of the nullptr is not undefined behaviour (answer on SO, discussion under issue 232 of C++ Standard, ...). Thus, the validity of /*1*/ is understandable under this assumption.

However, how is /*7*/ guaranteed to not cause UB? As per the cited quote, there is no conversion of lvalue to rvalue in p->static_mem_fn();. But the same is true for /*6*/ p->non_static_mem_fn();, and I think my guess is confirmed by the quote from the same issue 315 regarding:

(in the quote, I changed "which" and f() to get the connection to the notation used in this question).


So, why is such a distinction made for p->static_mem_fn(); and p->non_static_mem_fn(); regarding the causality of UB? Is there an intended use of calling static functions from pointers that could potentially be nullptr?


Appendix:

解决方案

Standard citations in this answer are from the C++17 spec (N4713).

One of the sections cited in your question answers the question for non-static member functions. [class.mfct.non-static]/2:

This applies to, for example, accessing an object through a different pointer type:

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

A null pointer doesn't point at any valid object, so it certainly doesn't point to an object of type A either. Using your own example:

p->non_static_mem_fn(); // UB by [class.mfct.non-static]/2

With that out of the way, why does this work in the static case? Let's pull together two parts of the standard:

[expr.ref]/2:

[class.static]/1 (emphasis mine):

The second block, in particular, says that the object expression is evaluated even for static member access. This is important if, for example, it is a function call with side effects.

Put together, this implies that these two blocks are equivalent:

// 1
p->static_mem_fn();

// 2
*p;
A::static_mem_fn();

So the final question to answer is whether *p alone is undefined behavior when p is a null pointer value.

Conventional wisdom would say "yes" but this is not actually true. There is nothing in the standard that states dereferencing a null pointer alone is UB and there are several discussions that directly support this:

  • Issue 315, as you have mentioned in your question, explicitly states that *p is not UB when the result is unused.
  • DR 1102 removes "dereferencing the null pointer" as an example of UB. The given rationale is:

  • This DR links to issue 232 where it is discussed to add wording that explicitly indicates *p as defined behavior when p is a null pointer, as long as the result is not used.

In conclusion:

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

这篇关于为什么在使用静态方法时取消引用 nullptr 而不是 C++ 中的未定义行为?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

07-23 06:42