问题描述
我正在阅读 一篇关于 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 具有
...*p
当 p
为空时不是错误,除非左值被转换为右值 (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
的指针调用静态函数?
附录:
- 这个问题询问为什么取消引用
nullptr
是未定义的行为.虽然我同意在大多数情况下这是一个坏主意,但我认为根据此处的链接和引号,该声明并非绝对正确. - 类似的讨论在本问答中,附有一些指向第 232 期的链接.
- 我找不到专门针对静态方法和
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
alone 在 p
是空指针值时是否是未定义的行为.
传统智慧会说是";但事实并非如此.标准中没有任何规定单独取消引用空指针是 UB 并且有几个讨论直接支持这一点:
- 问题 315,正如你在您的问题中提到,明确指出
*p
在结果未使用时不是 UB. - DR 1102 删除了取消引用空指针"以UB为例.给出的理由是:
围绕取消引用空指针的未定义行为存在核心问题.似乎意图是取消引用是定义良好的,但是使用取消引用的结果将产生未定义的行为.这个话题太混乱了,不能作为未定义行为的参考示例,或者如果要保留它应该更准确地说明.
- 此 DR 链接到 issue 232 当
p
为空指针时,添加明确表示*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:
- this question asks about why dereferencing a
nullptr
is undefined behaviour. While I agree that in most cases it is a bad idea, I do not believe the statement is absolutely correct as per the links and quotes here. - similar discussion in this Q/A with some links to issue 232.
- I was not able to find a question devoted to static methods and the
nullptr
dereferencing issue. Maybe I missed some obvious answer.
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 whenp
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++ 中的未定义行为?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!