C和C ++标准是否意味着地址空间中的特殊值必须仅存在才能表示空指针的值?

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

如果可以从C和C ++标准推断(答案可以针对两个标准),取消引用其值等于nullptr (或(void *)0 )值的指针变量是未定义的行为,是否意味着这些语言需要地址空间中的特殊值是死的 ,这意味着除了表示nullptr的角色之外它是不可用的? 如果系统在同一地址上具有真正有用的函数或数据结构等于nullptr会怎么样? 这应该永远不会发生,因为编译器编写的每个系统都要找出一个非冲突的空指针值,这是编译器的编写者的责任吗? 或者,在“未定义的行为模式”下编程以实现其意图时,需要访问此类函数或数据结构的程序员是否满足?

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

这篇博文详细介绍了解决问题的情况

这是否意味着这些语言要求地址空间中的特殊值是死的,这意味着除了表示nullptr的角色之外它是不可用的?

没有。

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

但是,如果该地址已存在某些内容,即没有严格符合的程序可以访问的内容,则允许实现支持取消引用空指针来访问它。 取消引用空指针在标准C中是未定义的,因此实现可以使它做任何它喜欢的事情,包括显而易见的事情。

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

5.1.2.3程序执行

10示例2执行片段

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

“整数提升”要求抽象机器将每个变量的值提升为int大小,然后添加两个int并截断总和。 如果可以在没有溢出的情况下添加两个char ,或者无声地溢出包装以产生正确的结果,则实际执行只需要产生相同的结果,可能省略促销。

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

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


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

这取决于“地址空间”这个短语的含义。 C标准使用非正式的短语,但没有定义它的含义。

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

因此,如果您定义“地址空间”以覆盖2 N个不同的地址,其中N是指针的位宽,则是,其中一个值必须保留为空指针值。

另一方面,如果“地址空间”比那个窄(例如,典型的64位系统实际上不能访问2 64个不同的存储器位置),那么保留为空指针的值可以很容易地在“地址空间“。

有些事情需要注意:

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

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

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

这是否意味着这些语言要求地址空间中的特殊值是死的,这意味着除了表示nullptr的角色之外它是不可用的?

是。

C对空指针有要求,使其与对象指针不同:

(C11,6.3.2.3p3)“[…]如果将空指针常量转换为指针类型,则保证将结果指针(称为空指针)与指向任何对象或函数的指针进行比较。

如果系统在同一地址上具有真正有用的函数或数据结构等于nullptr会怎么样? 这是否永远不会发生,因为编译器编写的每个系统都需要找出一个非冲突的空指针值?

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

对于许多实现,所有位零都是空指针常量的方便的执行时表示,因为它总是存储中的最低地址。 (INMOS Transputer [632]有一个签名的地址空间,在中间放置零。)虽然在这个位置可能有程序引导程序信息,但是不太可能在这里放置任何对象或函数。 许多操作系统都不使用此存储位置,因为经验表明程序错误有时会导致值写入空指针常量指定的位置(更多面向开发人员的环境尝试在访问该位置时引发exception)。

当主机环境不包括地址零作为进程地址空间的一部分时,另一种实现技术是创建一个对象(有时称为_ _null)作为标准库的一部分。 对空指针常量的所有引用都引用此对象,其地址将与任何其他对象或函数进行比较。

是的,这正是它的意思。

[C++11: 4.10/1]: [..]空指针常量可以转换为指针类型; 结果是该类型的空指针值 ,并且可以与对象指针或函数指针类型的每个其他值区分开。 [..]

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

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

如果操作系统将一个非常有用的函数或数据结构放在等于nullptr的同一地址怎么办?

操作系统不会这样做,但它可以被利用 。