C 和 C++ 标准是否暗示地址 space 中的特殊值必须仅存在以表示空指针的值?

Do C and C++ standards imply that a special value in the address space must exist solely to represent the value of null pointers?

关于 C 和 C++ 中的空指针的讨论之后,我想在这里分开结束问题。

如果可以从 C 和 C++ 标准(答案可以同时针对这两个标准)推断取消引用其值等于 nullptr(或 (void *)0)值的指针变量是未定义的行为,是不是暗示这些语言要求地址space中有一个特殊的值是dead,意思是除了代表nullptr的作用外不能用?如果系统在等于 nullptr 的相同地址处有一个真正有用的函数或数据结构怎么办?这永远不会发生,因为编译器的编写者有责任为编译器编译到的每个系统找出一个不冲突的空指针值吗?或者需要访问此类函数或数据结构的程序员是否应该在 "undefined behavior mode" 中编程以实现其意图?

这看起来像是模糊了编译器和计算机系统的角色界限。我会问这样做是否正确,但我想这里没有空间。

This blog post digs about tackling the problem situation

是的,正是这个意思。

[C++11: 4.10/1]: [..] A null pointer constant can be converted to a pointer type; the result is the null pointer value of that type and is distinguishable from every other value of object pointer or function pointer type. [..]

空指针值不需要是0x00000000,但它确实需要是唯一的;没有其他方法可以使这条规则生效。

这当然不是抽象机器的唯一规则,它隐含地对实际实现施加严格限制。

What if the OS puts a really useful function or data structure at the same address that's equal to nullptr?

OS 不会那样做,但是 it can be exploited

这取决于短语 "address space" 的含义。 C 标准非正式地使用该短语,但未定义其含义。

对于每个指针类型,必须有一个 value(空指针)比较不等于指向任何对象或函数的指针。这意味着,例如,如果指针类型为 32 位宽,则该类型最多可以有 232-1 个有效的非空值。如果某些地址具有不止一种表示,或者如果不是所有表示都对应于有效地址,则可能会更少。

因此,如果您将 "address space" 定义为覆盖 2N 个不同的地址,其中 N 是指针的位宽度,那么是的,这些值之一必须保留为空指针值。

另一方面,如果 "address space" 比它窄(例如,典型的 64 位系统实际上不能访问 264 个不同的内存位置), 那么保留为空指针的值很容易在 "address space".

之外

一些注意事项:

  • 空指针的表示可能是也可能不是全零。
  • 并非所有指针类型都必须具有相同的大小。
  • 并非所有指针类型都必须对空指针使用相同的表示形式。

在大多数现代实现中,所有指针类型 都是 相同的大小,并且都将空指针表示为全零,但是有充分的理由,例如, 使函数指针比对象指针更宽,或者使 void*int* 更宽,或者对空指针使用除全零之外的表示。

此答案基于 C 标准。其中大部分也适用于 C++。 (一个区别是 C++ 具有指向成员的指针类型,通常比普通指针更宽。)

是不是暗示这些语言要求地址space中的一个特殊值已经死了,意思是除了代表nullptr的作用外不能用?

是的。

C 对空指针的要求使其不同于对象指针:

(C11, 6.3.2.3p3) "[...] 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."

如果系统在等于 nullptr 的相同地址处有一个真正有用的函数或数据结构怎么办?这永远不会发生,因为编译器编写者有责任为编译器编译到的每个系统找出一个不冲突的空指针值吗?

Derek M. Jones 的新 C 标准提供了以下关于实现的评论:

All bits zero is a convenient execution-time representation of the null pointer constant for many implementations because it is invariably the lowest address in storage. (The INMOS Transputer[632] had a signed address space, which placed zero in the middle.) Although there may be program bootstrap information at this location, it is unlikely that any objects or functions will be placed here. Many operating systems leave this storage location unused because experience has shown that program faults sometimes cause values to be written into the location specified by the null pointer constant (the more developer-oriented environments try to raise an exception when that location is accessed).

Another implementation technique, when the host environment does not include address zero as part of a processes address space, is to create an object (sometimes called _ _null) as part of the standard library. All references to the null pointer constant refer to this object, whose address will compare unequal to any other object or function.

does it imply that these languages require that a special value in the address space is dead, meaning that it's unusable except for the role of representing nullptr?

没有

编译器需要一个特殊的值来表示一个空指针,并且必须注意它不会在该地址放置任何对象或函数,因为所有指向对象和函数的指针都需要与空指针比较不相等.标准库在实现 malloc 和朋友时必须采取类似的预防措施。

但是,如果那个地址已经有东西,没有严格符合的程序可以访问的东西,那么允许实现 支持取消引用空指针来访问它。取消引用空指针在标准 C 中是未定义的,因此实现可以让它做任何它喜欢的事情,包括显而易见的事情。

C 和 C++ 标准都理解 as-if 规则的概念,这基本上意味着对于有效输入,实现与符合标准,那么它确实符合标准。 C 标准使用了一个简单的例子:

5.1.2.3 Program execution

10 EXAMPLE 2 In executing the fragment

char c1, c2;
/* ... */
c1 = c1 + c2;

the "integer promotions" require that the abstract machine promote the value of each variable to int size and then add the two ints and truncate the sum. Provided the addition of two chars can be done without overflow, or with overflow wrapping silently to produce the correct result, the actual execution need only produce the same result, possibly omitting the promotions.

现在,如果 c1c2 的值来自寄存器,并且可以强制 char 范围之外的值进入这些寄存器(例如通过内联汇编),那么可以观察到实现优化了整数提升这一事实。但是,由于观察它的唯一方法是通过未定义的行为或实现扩展,因此任何标准代码都无法受此影响,并且允许实现这样做。

这与在取消引用空指针时获得有用结果的逻辑相同:只有两种方法可以从代码中看出在该特定地址有一些有意义的东西:从评估中获取空指针保证产生指向对象的指针,或者只是尝试它。前者是我提到的编译器和标准库必须处理的。后者不会影响有效的标准程序。


一个众所周知的例子是 DOS 实现上的中断向量 table,它位于地址零。通常只需取消引用空指针即可访问它。 C 和 C++ 标准没有、不应该也不能涵盖对中断向量的访问 table。他们没有定义此类行为,但也不限制对其的访问。实现应该并且被允许提供扩展来访问它。