【问题标题】:What's behind NULL?NULL 的背后是什么?
【发布时间】:2016-07-15 20:20:11
【问题描述】:

在许多 C 实现中(但不是 all),NULL 对应于进程的逻辑地址 0。当我们尝试取消引用 NULL 时,我们会遇到分段错误,因为 NULL“映射”到内存既不可读也不可写的段。

是否存在具有对应于虚拟NULL 地址的真实物理地址的系统?如果是这样,那里可能存储什么?还是NULL 纯粹是一个没有物理对应物的虚拟地址?

【问题讨论】:

  • 空指针是一个概念,NULL 是该概念的实现(编译器)实现。的确,在许多平台上,空指针由地址0 表示(如(void *) 0),但特定于实现的部分应该无关紧要。从您作为程序员的角度来看,空指针就是空指针,仅此而已。
  • 取消引用 NULL 不一定会导致崩溃,它是未定义的行为。鼻恶魔。并非所有可以使用 C 的系统都有类似于分段错误的概念。
  • NULL 可能有也可能没有对应的物理地址。无论哪种情况,它都符合 C 标准。 C 标准所说的是 undefined 取消引用 NULL。所以(很多)你的问题的答案是“视情况而定”。
  • @John Bode 我认为 OP 正在寻找超出 C 规范的 NULL 的一些已定义属性。 NULL 可能是无效地址。 NULL 可能是有效地址。 NULL 可能/可能不会耦合到特定的物理地址。当然同意它“保证与程序中任何对象或函数的地址比较不相等”。

标签: c memory null


【解决方案1】:

NULL定义的实现

它是一个扩展为实现定义的宏空指针常量

"it depends" @I3x

是否存在对应于虚拟 NULL 地址的真实物理地址?

在某些平台上:是的,在其他平台上:不是。它是实现定义的。

如果是这样,那里可能存储什么?

在某些平台上:一个值,可能是 0。这个值可能/可能无法访问。在其他方面:没有简单的访问权限。它是实现定义的。

或者NULL 纯粹是一个没有物理对应物的虚拟地址?

在某些平台上:它是一个没有对应物的虚拟地址。在其他人身上,它可能是一个真实的地址。它是实现定义的。

【讨论】:

  • NULL 宏是实现定义的这一事实并不特别相关。它允许实现将其定义为0,或((void*)0,或('-'-'-'),但它的定义是在转换为指针类型时产生一个空指针
  • @ Keith Thompson 关于宏的细节源于 C 规范如何定义它——因此从 C 定义开始是有意义的。关于NULL,这是一个棘手的部分:它可能不会产生指针。它产生一个空指针常量。正如你所说,当NULL转换为指针时,它是一个空指针
【解决方案2】:

来自 C 标准文件 ISO/IEC 9899:201x (C11) §6.3.2.3 Pointers

  1. 值为 0 的整数常量表达式,或这样的 将表达式转换为 void * 类型,称为空指针 持续的。如果将空指针常量转换为指针 类型,结果指针,称为空指针,保证 比较不等于指向任何对象或函数的指针

这意味着如果与 0 值相比,NULL 指针应该给出结果 TRUE,并且如果转换为算术类型,它将解析为 value=0。
但这也意味着编译器必须为空指针生​​成一个值,该值保证不是代码运行环境中存在(比较相等)的地址(这应该更规范)。
范围是使代码可移植,强制对 NULL 指针的每个操作必须在任何 CPU 或体系结构上给出相同的结果

注意: 在实际操作中,为了保证 NULL 指针不等于运行环境中的任何地址,有两种方法。第一个将适用于存在无效地址的平台,利用这些无效地址。第二种是按惯例为范围保留一个地址,通常为 0。后者意味着平台,主要是操作系统,必须注意使该地址无效,因为它不可用,然后可以从不等于任何其他指针,否则总会有一个对象可以合法地使用该地址,从而使其与 NULL 指针的地址比较有效。这一点很重要,因为缺少任何基础设施,就像操作系统一样,可以处理无效值,我们可能会遇到冲突的情况。例如,当使用 C 编译器进行基础编程、操作系统和内核时,如果处理器使用地址 0 的内存来保存中断服务地址,则指向对象的指针将具有值 0。在这种情况下,编译器应该使用不同的NULL 指针的值,但如果操作系统可以访问整个寻址空间。为了允许完整的内存管理,将没有适用于 NULL 指针的值。
在用户或内核 HL 代码上,这不是问题,因为总有一些地址通常是不允许的,然后可用于 NULL 指针值。

【讨论】:

  • 对于目标系统来说,空指针不需要是无效地址。它只是必须比较不等于指向任何对象或函数的指针。例如,就 CPU 而言,有些系统的所有地址都是有效的;实现只需避免将任何内容分配给空指针所指的地址。取消引用空指针具有未定义的行为;它不需要造成任何陷阱。
【解决方案3】:

是的,大多数(所有?)系统都有一个真实的物理地址为零,它只是无法从运行在将虚拟地址重新映射到(不同的)物理地址的虚拟地址空间后面的进程访问。在这些进程中,地址零只是简单地未映射到任何东西,就像它们的大部分地址空间一样。

然而,事实上,直到今天,所有基于 x86 的处理器都首先启动到“实模式”。这是一种 8086 兼容模式。在这种处理器模式下,不仅有一个物理可寻址地址为零,而且这个地址实际上就是中断向量表所在的位置,也是处理器在处理中断时读取的位置。

【讨论】:

    【解决方案4】:

    当然,在某些计算机上,地址 0 处有实际内存。在其他计算机上,可能没有。甚至有些硬件设计的答案可能取决于内存映射芯片或芯片组的临时状态。

    但在所有情况下,取消引用 NULL 指针都是未定义的行为。

    【讨论】:

      【解决方案5】:

      在许多 C 实现(但不是全部)中,NULL 对应于进程的逻辑地址 0。

      不,这是错误的看待它的方式。根据定义,空指针不对应于自身以外的任何东西,并且 C 绝不会将空指针与任何特定地址联系起来,在任何意义上。

      充其量,您可以说某些实现的 表示 空指针具有指向位于机器地址 0 的对象的指针的表示形式。然而,由于空指针与指向对象或函数的每个指针比较不相等,因此这种情况要求 C 程序可以引用的任何对象或函数都不可能驻留在该地址。

      当我们尝试取消对 NULL 的引用时,我们会遇到分段错误

      这是您调用未定义行为时可能观察到的众多行为之一。根据经验,当以您描述的特定方式调用未定义的行为时,它是最有可能被观察到的一种。 C 决不能保证这一点,事实上,根据经验,有一些广泛使用的实现表现出不同的行为。

      因为 NULL “映射”到既不可读也不可写的内存段。

      但是您正在深入研究实现细节。

      是否存在与虚拟 NULL 地址对应的真实物理地址?

      不,因为空指针不是地址。它是一个指针值,对取消引用显式无效。从技术上讲,尽管 C 将某些指针值称为“地址”,但它既不需要也不依赖任何指针值是 机器 地址,既不是物理地址也不是虚拟地址。然而,即使在 C 的术语意义上,空指针的值也不是地址。 & 运算符从不产生这个值,并且取消引用是无效的——它明确地无法对应任何对象或函数。

      如果是这样,那里可能存储什么?还是 NULL 纯粹是一个没有物理对应物的虚拟地址?

      虽然空指针值的表示方式确实很常见,就像指向位于机器地址 0 的对象/函数的指针一样,但这无关紧要,因为在这种情况下,C 程序永远无法访问居住在该地址的任何东西。因此,就程序而言,那里没有任何东西。另一方面,就硬件和操作系统内核而言,外部程序对内存的看法在很大程度上是无关紧要的。如果仍然坚持考虑内核对物理内存中存储在从外部程序的空指针表示派生的地址中的内容的看法,那么它是高度依赖于系统的。

      【讨论】:

      • “从技术上讲,no 指针值对于 C 而言是一个本身的地址......”。实际上,就 C 而言,all 有效的非空指针值都是“地址”。但是“地址”不一定与“机器地址”相同。例如,C 标准说“一元 & 运算符产生其操作数的地址。”
      • 问题是关于可能的实现。
      • @KeithThompson,你的观察和你对不同类型地址的区别是很好的。然而,我认为,即便如此,C 只说在某种意义上存在地址,并且这些是指针值,不是所有指针值都是这种意义上的地址。当然,指针类型的定义避免了“地址”一词。为了更准确,我已经重写了我的答案。
      猜你喜欢
      • 2018-02-19
      • 1970-01-01
      • 2012-05-15
      • 2010-09-15
      • 2023-03-21
      • 2011-06-20
      • 2013-01-02
      • 2012-04-17
      • 2012-11-30
      相关资源
      最近更新 更多