C++20中解引用空指针是否为UB?典型场景合规性问询
问题起源
最初的研究围绕C语言中&((T*)NULL)->member是否属于UB展开——这是offsetof宏的经典旧实现方式。但进一步探究发现,C无法用该方式实现offsetof(非标准布局类型下明确不允许),进而转向核心问题:**C20中解引用空指针本身是否属于UB?**
标准与CWG议题的历史演变
早期C++标准相关草案(如n1146)曾将"解引用空指针"列为UB的示例,但该示例在CWG issue #1102中被移除,理由是该话题存在核心争议:有观点认为解引用空指针本身是良定义的,只有将解引用得到的左值转换为右值时才会触发UB,这种混乱使得它不适合作UB的参考示例。
早期支持"良定义"的依据
CWG issue #232和#315是支持"解引用空指针本身非UB"的关键依据,相关讨论针对以下场景:
#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- CWG issue #232指出
p=0;*p;本身并非错误,仅当对该左值进行左值到右值转换时才会产生UB - CWG issue #315进一步明确:上述示例中,空指针
d解引用得到的左值并未被转换为右值,因此*d合法,进而d->fun()和d->a的访问也合法
C++20的标准变化
C++20标准(n4861)中对->运算符的规定明确:E1必须是指针类型的纯右值(prvalue)。这引发疑问:此处是否隐含左值到右值转换?
根据C++20标准条款expr.unary#op-1.sentence-3,仅定义了指针指向有效对象时的解引用行为,未明确空指针解引用的情况。这意味着标准未覆盖的行为默认属于UB——尽管这可能并非标准制定者的原始意图。
后续CWG issue #198的讨论也确认了这一点:空指针解引用因标准遗漏被视为UB。此外,CWG issue #232已关闭,相关调整参考议题2823,但该变更不适用于C++20。
具体场景分析
1. 空指针访问静态成员(如d->fun())
虽然早期CWG议题支持该场景为良定义,但在C++20中,由于标准仅定义了有效指针的解引用行为,该场景严格来说属于UB。不过多数主流编译器会将其优化为直接访问静态成员,实际运行不会出错,但这是编译器的扩展行为,而非标准保证。
2. &((T*)NULL)->member(offsetof旧实现)
- 对于标准布局类型:C++标准明确要求offsetof宏的实现是良定义的,但直接使用
&((T*)NULL)->member并非标准允许的写法,属于UB(即使效果等价于offsetof)。 - 对于非标准布局类型:该写法本身就是UB,因为非标准布局类型的成员偏移无法通过这种方式可靠计算。
结论
在C++20标准下:
- 空指针解引用本身(
*nullptr)属于未定义行为,因为标准仅定义了指针指向有效对象时的解引用行为,未覆盖空指针场景。 - 空指针访问静态成员、
&((T*)NULL)->member等场景均属于UB,尽管部分编译器会按预期处理,但这不属于标准保证的行为。
内容的提问来源于stack exchange,提问作者Mes

