Do C和C++标准意味着地址空间中必须只存在一个特殊值来表示空指针的值



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

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

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

这篇博客文章挖掘了

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

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

因此,如果您定义"地址空间"来覆盖2N不同的地址,其中N是指针的位宽,那么是的,必须保留其中一个值作为空指针值。

另一方面,如果"地址空间"比它窄(例如,典型的64位系统实际上不能访问264不同的内存位置),那么作为空指针保留的值可以很容易地在"地址空间"之外。

需要注意的一些事项:

  • 空指针的表示可以是也可以不是所有比特都为零
  • 并非所有指针类型的大小都必须相同
  • 并非所有指针类型都必须对空指针使用相同的表示形式

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

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

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

没有。

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

然而,如果在该地址上已经有了某种东西,任何严格符合程序都无法访问,那么实现就可以支持取消引用空指针来访问它。在标准C中,取消引用空指示器是未定义的,因此实现可以让它做任何它喜欢的事情,包括显而易见的事情。

C和C++标准都将的概念理解为规则,这基本上意味着,如果对于有效输入,一个实现与符合标准的实现无法区分,那么它是否符合标准。C标准使用了一个微不足道的例子:

5.1.2.3程序执行

10示例2在执行片段时

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

"整数提升"要求抽象机将每个变量的值提升到int大小,然后将两个int相加并截断总和。如果可以添加两个char,则无需溢出,或者使用溢出静默包装来产生正确的结果,实际执行只需要产生相同的结果,可能会省略提升。

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

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


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

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

是的。

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

(C11,6.3.2.3p3)"[…]如果将空指针常量转换为指针类型,则生成的指针(称为空指针)保证与指向任何对象或函数的指针相比不相等。"

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

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

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

另一种实现技术,当主机环境不包括作为处理地址空间,就是创建一个对象(有时称为_null)作为标准库的一部分。全部的对空指针常量的引用引用了该对象,该对象的地址将与其他对象的地址不相等对象或函数。

是的,这正是它的意思。

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

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

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

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

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

最新更新