取消引用一个等于 nullptr 的指针是标准未定义的行为吗?

Is dereferencing a pointer that's equal to nullptr undefined behavior by the standard?

一位博客作者提出了关于空指针解引用的讨论:

我在这里提出了一些反驳论点:

他引用标准的推理主线是这样的:

The '&podhd->line6' expression is undefined behavior in the C language when 'podhd' is a null pointer.

The C99 standard says the following about the '&' address-of operator (6.5.3.2 "Address and indirection operators"):

The operand of the unary & operator shall be either a function designator, the result of a [] or unary * operator, or an lvalue that designates an object that is not a bit-field and is not declared with the register storage-class specifier.

The expression 'podhd->line6' is clearly not a function designator, the result of a [] or * operator. It is an lvalue expression. However, when the 'podhd' pointer is NULL, the expression does not designate an object since 6.3.2.3 "Pointers" says:

If a null pointer constant is converted to a pointer type, the resulting pointer, called a null pointer, is guaranteed to compare unequal to a pointer to any object or function.

When "an lvalue does not designate an object when it is evaluated, the behavior is undefined" (C99 6.3.2.1 "Lvalues, arrays, and function designators"):

An lvalue is an expression with an object type or an incomplete type other than void; if an lvalue does not designate an object when it is evaluated, the behavior is undefined.

So, the same idea in brief:

When -> was executed on the pointer, it evaluated to an lvalue where no object exists, and as a result the behavior is undefined.

这个问题纯粹是基于语言的,我不是在问给定的系统是否允许用任何语言篡改位于地址 0 的内容。

据我所知,取消引用值等于 nullptr 的指针变量没有任何限制,甚至认为指针与 nullptr(或 (void *) 0) 由于所述段落,在某些情况下,常量可能会在优化中消失,但这看起来像是另一个问题,它不会阻止取消引用其值等于 nullptr 的指针。请注意,我已经检查了其他 SO 问题和答案,特别是 like this set of quotations,以及上面的标准引用,但我没有偶然发现从标准中可以清楚地推断出如果指针 ptr比较等于 nullptr,取消引用它将是未定义的行为。

至多我得到的是引用常量(或将其强制转换为任何指针类型)是 UB,但没有提到一个位等于来自 nullptr.

的值

我想清楚地将 nullptr 常量与一个指针变量分开,指针变量的值等于它。但是解决这两种情况的答案是理想的。

我确实意识到,当与 nullptr 等进行比较时,优化可以快速进行,并且可能会基于此简单地删除代码。

如果结论是,如果ptr等于nullptr解引用的值肯定是UB,还有一个问题是:

当您引用 C 时,取消引用空指针显然是此标准引用(强调我的)中的未定义行为:

(C11, 6.5.3.2p4) "If an invalid value has been assigned to the pointer, the behavior of the unary * operator is undefined.102)"

102): "Among the invalid values for dereferencing a pointer by the unary * operator are a null pointer, an address inappropriately aligned for the type of object pointed to, and the address of an object after the end of its lifetime."

C99 中的引用完全相同,C89/C90 中的引用类似。

C++

dcl.ref/5.

There shall be no references to references, no arrays of references, and no pointers to references. The declaration of a reference shall contain an initializer (8.5.3) except when the declaration contains an explicit extern specifier (7.1.1), is a class member (9.2) declaration within a class definition, or is the declaration of a parameter or a return type (8.3.5); see 3.1. A reference shall be initialized to refer to a valid object or function. [ Note: in particular, a null reference cannot exist in a well-defined program, because the only way to create such a reference would be to bind it to the “object” obtained by indirection through a null pointer, which causes undefined behavior. As described in 9.6, a reference cannot be bound directly to a bit-field. — end note ]

这个注释很有趣,因为它明确表示取消引用空指针是未定义的。

我确定它在更相关的上下文中的其他地方说过,但这已经足够了。

我看到的这个问题的答案是,关于 NULL 值可以取消引用的程度,是否故意以未指定的方式留下 platform-dependent,因为 implementation-defined 在C11 6.3.2.3p5 和 p6。这主要是为了支持用于为平台开发引导代码的独立实现,正如 OP 在他的反驳中指出的那样 link,但也有用于托管实现的应用程序。

回复:
(C11, 6.5.3.2p4) "If an invalid value has been assigned to the pointer, the behavior of the unary * operator is undefined.102)"

102): "Among the invalid values for dereferencing a pointer by the unary * operator are a null pointer, an address inappropriately aligned for the type of object pointed to, and the address of an object after the end of its lifetime."

这是原样,afaict,因为脚注中的每个案例对于编译器所针对的特定平台可能并非无效。如果那里有缺陷,它的 "invalid value" 应该是斜体并由 "implementation-defined" 限定。对于对齐情况,平台可能能够使用任何地址访问任何类型,因此没有对齐要求,尤其是在支持地址翻转的情况下;并且平台可能假设 object 的生命周期仅在应用程序退出后结束,通过 malloc() 为每个函数调用的自动变量分配一个新框架。

对于空指针,在启动时平台可能期望处理器使用的结构具有特定的物理地址,包括地址 0,并在源代码中表示为 object 指针,或者可能需要定义引导过程以使用基地址 0 的函数。如果标准不允许取消引用,如“&podhd->line6”,其中平台要求 podhd 的基地址为 0,则需要汇编语言来访问那个结构。类似地,软重启函数可能需要取消引用 0 值指针作为 void 函数调用。托管实现可能会将 0 视为可执行映像的基址,并在加载后将源代码中的 NULL 指针映射到该映像的 header,因为该结构需要位于该实例的逻辑地址 0 C虚拟机.

标准调用的指针更多的是指向虚拟机虚拟地址space的句柄,其中object句柄对它们允许的操作有更多的要求。编译器如何发出将这些句柄的要求考虑到特定处理器的代码未定义。毕竟,对一个处理器有效的东西可能对另一个处理器无效。

对 (void *)0 的要求更多是编译器发出代码来保证源使用 (void *)0 的表达式,显式地或通过引用 NULL,存储的实际值将是这样的不能通过任何映射代码指向任何有效的函数定义或 object。这不一定是 0!类似地,对于 (void *)0 到 (obj_type) 和 (func_type) 的强制转换,这些只需要获得分配的值,这些值评估为地址编译器保证不被用于 objects 或代码。与后者的区别在于它们是未使用的,不是无效的,因此能够以定义的方式取消引用。

然后,测试指针相等性的代码将检查一个操作数是否是这些值之一,而另一个操作数是否是 3 个值之一,而不仅仅是相同的位模式,因为这会将它们标记为 RTTI ( null *) 类型,不同于指向已定义实体的 void、obj 和 func 指针类型。该标准可能更明确它是一个独特的类型,如果未命名是因为编译器仅在内部使用它,但我认为通过 "null pointer" 被斜体化这被认为是显而易见的。实际上,imo,这些上下文中的“0”是编译器的附加关键字标记,因为它需要识别 (null *) 类型,但不具有这样的特征,因为这会使 < 的定义复杂化标识符 >.

此存储值可以是 SIZE_MAX,就像 0 一样容易,对于 (void *)0,在执行时发出的应用程序代码中,例如,定义范围 0 到 SIZE_MAX- 4*sizeof(void *) 虚拟机句柄对代码和数据有效。 NULL 宏甚至可以定义为
(void *)SIZE_MAX,编译器可以从上下文中找出它与 0 具有相同的语义。转换代码负责注意它是在指针 <--> 指针转换中选择的值,并提供适合作为 object 或函数指针的内容。从指针 <--> 整数转换,隐式或显式,具有类似的检查和供应要求;特别是在 (u)intptr_t 字段覆盖 (type *) 字段的联合中。可移植代码可以防止编译器使用显式 *(ptr==NULL?(type *)0:ptr) 表达式不正确地执行此操作。