我从研究问题“在C中,
我知道根据cppreference页面,C++现在无法实现
但是在阅读一些C++ CWS问题后,我的问题变成了“解引用空指针是否是未定义行为”。
此外,我认为他们不会没有任何理由地改变
一开始,我以为会有一个明确指定“解引用空指针是未定义行为”的术语。
然而,随着我深入了解,我发现这比我想象的要复杂得多。
在阅读了很多stackoverflow文章回复后,我发现答案并不统一。
有些帖子说它是明确定义的,有些帖子说它是未定义行为,还有些帖子说它是非指定的。
对于那些认为这是明确定义的帖子,他们引用了“CWG issue #232”和“CWG issue #315”作为理由,就像在c++ access static members using null pointer中的答案一样。
对于那些认为这是未指定的帖子,他们说标准中没有明确指定。
对于那些认为这是未定义行为的帖子,他们说该问题尚未包含在标准中,因此仍然是未定义行为。此外,他们提到了“如果将无效值分配给指针,则一元运算符
上述stackoverflow中的示例为:
他们说这是明确的原因大致如下:
這個問題在2010年被討論過,已經過去了13年了,所以我認為這個問題已經存在很長時間了,但可惜的是,我現在還是找不到答案。
總而言之,是否可以由一位語言專家給我一個關於這個問題的結論?在C++20中,解引用空指針是未定義行為嗎?例如,
希望能提供相關的歷史和標準術語。
&((T*)NULL)->member
是未定义行为吗?”开始。这是我的教科书中的一个例子,介绍了旧版的offsetof
实现。我知道根据cppreference页面,C++现在无法实现
offsetof
。但是在阅读一些C++ CWS问题后,我的问题变成了“解引用空指针是否是未定义行为”。
此外,我认为他们不会没有任何理由地改变
offsetof
的实现方式,即&((T*)NULL)->member
,但我不知道原因,也许是因为它是未定义行为?但我没有找到一个术语说&((T*)NULL)->member
在C中是未定义行为。对于C++来说,如果它不是标准布局类型,我认为它是未定义行为。一开始,我以为会有一个明确指定“解引用空指针是未定义行为”的术语。
然而,随着我深入了解,我发现这比我想象的要复杂得多。
在阅读了很多stackoverflow文章回复后,我发现答案并不统一。
有些帖子说它是明确定义的,有些帖子说它是未定义行为,还有些帖子说它是非指定的。
对于那些认为这是明确定义的帖子,他们引用了“CWG issue #232”和“CWG issue #315”作为理由,就像在c++ access static members using null pointer中的答案一样。
对于那些认为这是未指定的帖子,他们说标准中没有明确指定。
对于那些认为这是未定义行为的帖子,他们说该问题尚未包含在标准中,因此仍然是未定义行为。此外,他们提到了“如果将无效值分配给指针,则一元运算符
*
的行为是未定义的。”这个术语。上述stackoverflow中的示例为:
#include <iostream>
class demo {
public:
static void fun()
{
std::cout << "fun() is called\n";
}
static int a;
};
int demo::a = 9;
int main()
{
demo *d = nullptr;
d->fun();
std::cout << d->a;
return 0;
}
他们说这是明确的原因大致如下:
E1->E2
等同于(*(E1)).E2
- 因此,如果
*d;
是合法的,那么d->fun()
也是合法的。 - CWG issue #232 表示
p = 0; *p;
并不是本质上的错误。左值到右值的转换会导致其行为未定义。 - CWG issue #315 表示在上述示例中,当
d
为 null 时,*d
不是错误,除非将左值转换为右值(7.3.2 [conv.lval]),但在这里并没有进行转换。 - 因此,
*d;
是合法的,那么d->fun()
也是合法的。
这个问题在大约2005年左右讨论过,当时还是在C++03规范中。
然而,在C++20中,对于->
,标准明确指定了E1
在E1->E2
中应该是prvalue:
n4861(expr.ref#2):对于第二种选项(箭头),第一个表达式必须是具有指针类型的prvalue。
所以我认为在这里可能会有一个lvalue-to-rvalue转换,因为E1
应该是prvalue。
顺便说一下,在之前的标准中,"dereference null pointer"被用作未定义行为的例子。
n1146(intro.execution#4):这个国际标准中描述了某些其他操作为未定义行为(例如,解引用空指针的效果)。
但是这个例子在CWG issue #1102中被修改了。他们给出的原因是
對於解引用空指針的未定義行為存在著核心問題。看起來意圖是解引用明確定義的,但使用解引用結果會產生未定義行為。這個話題太混亂了,不適合作為未定義行為的參考例子,或者如果要保留的話應該陳述得更明確。這個問題在2010年被討論過,已經過去了13年了,所以我認為這個問題已經存在很長時間了,但可惜的是,我現在還是找不到答案。
總而言之,是否可以由一位語言專家給我一個關於這個問題的結論?在C++20中,解引用空指針是未定義行為嗎?例如,
&((T*)NULL)->member
和上面的d->fun()
。或者是未指定行為還是未規定行為?希望能提供相關的歷史和標準術語。
编辑:
我的总结是这仍然是一个未解决的问题,目前它总是通过省略UB(未定义行为)来进行操作,expr.unary#op-1.sentence-3只在指针指向对象时才定义了行为。但这可能不是预期的规范。
顺便说一下,有一个最新的讨论涉及到相同的结果:https://github.com/cplusplus/CWG/issues/198
请查看@user17732522的评论和@Brian Bi的回答
&((T*)NULL)->member
是你在C++中永远不会写的东西,即使没有空指针。最好的情况是std::dynamic_cast<T*>(&ptr)
(即使使用dynamic_cast通常也意味着设计上的缺陷)。你能解释一下为什么解引用nullptr是个问题吗? - Pepijn Kramer#define 0
,因此不具备类型安全性。 - Pepijn Kramerd->fun()
和(*d).fun()
是等价的,并且在两者中都对d
进行了左值到右值的转换,但没有对*d
进行转换。问题是在*d
上假设了一个左值到右值的转换,例如,如果d
的类型是int*
,则可以写成*d + 1
。 - user17732522