为什么地址零用于空指针?

时间:2010-05-03 17:16:56

标签: c++ c memory pointers

在C(或C ++)中,指针是特殊的,如果它们的值为零:我建议在释放内存后将指针设置为零,因为这意味着再次释放指针并不危险;当我调用malloc时,如果它无法获取内存,则返回一个值为零的指针;我总是使用if (p != 0)来确保传递的指针是有效的等等。

但是,由于内存寻址从0开始,因为有效地址不是0而不是0吗?如果是这样的话,0如何用于处理空指针?为什么不是负数而是null?


编辑:

一堆好的答案。我将总结在我自己的思想解释中所表达的答案中所说的内容,并希望如果我误解,社区将纠正我。

  • 与编程中的其他内容一样,它是一种抽象。只是一个常量,与地址0并不真正相关.C ++ 0x通过添加关键字nullptr来强调这一点。

  • 它甚至不是地址抽象,它是C标准指定的常量,编译器可以将其转换为其他数字,只要它确保它永远不等于“真实”地址,并等于其他null指针,如果0不是用于平台的最佳值。

  • 如果它不是抽象,早期就是这种情况,系统使用地址0,程序员不受限制。

  • 我承认,我的负面数字建议有点疯狂的头脑风暴。对地址使用有符号整数有点浪费,如果它意味着除了空指针(-1或其他)之外,值空间在创建有效地址的正整数和刚刚浪费的负数之间均匀分配。

  • 如果任何数字总是可以用数据类型表示,那么它就是0.(也可能是1。我想的是一位整数,如果是无符号则为0或1,或者如果有符号则只是有符号位,或者是两位整数,它们是[-2,1]。但是你可以得到0为空,1是内存中唯一可访问的字节。)

仍有一些问题在我脑海中无法解决。 Stack Overflow问题 Pointer to a specific fixed address 告诉我,即使0为空指针是一个抽象,其他指针值也不一定。这导致我发布另一个Stack Overflow问题, Could I ever want to access the address zero?

21 个答案:

答案 0 :(得分:61)

2分:

  • 源代码中只有常量值0是空指针 - 编译器实现可以在运行代码中使用它想要或需要的任何值。某些平台有一个特殊的指针值,该值是“无效的”,实现可能会将其用作空指针。 C FAQ有一个问题"Seriously, have any actual machines really used nonzero null pointers, or different representations for pointers to different types?",它指出了几个使用0属性的平台是C源代码中的空指针,而在运行时则表示不同。 C ++标准有一个注释,清楚地表明转换“一个值为零的整数常量表达式总是产生一个空指针,但转换其他碰巧有零值的表达式不需要产生一个空指针”。

  • 负值可能与平台一样可用作地址--C标准只需选择用于指示空指针的东西,并选择零。老实说,我不确定是否考虑了其他哨兵价值。

空指针的唯一要求是:

  • 保证比较不等于指向实际对象的指针
  • 任何两个空指针都会比较相等(C ++对此进行了细化,以便只需要保存指向同一类型的指针)

答案 1 :(得分:29)

从历史上看,从0开始的地址空间始终是ROM,用于某些操作系统或低级中断处理例程,如今,由于一切都是虚拟的(包括地址空间),操作系统可以将任何分配映射到任何地址,所以它可以特别不在地址0分配任何东西。

答案 2 :(得分:15)

IIRC,“空指针”值不保证为零。编译器将0转换为适合于系统的任何“空”值(实际上它可能总是为零,但不一定是)。无论何时将指针与零进行比较,都会应用相同的转换。因为您只能将指针相互比较并与此特殊值0进行比较,所以它使程序员无法了解有关系统内存表示的任何信息。至于为什么他们选择0而不是42或者某些,我猜它是因为大多数程序员从0开始计数:)(另外,在大多数系统上0是第一个内存地址,他们希望它方便,因为在像我所描述的那样实践翻译很少实际发生;语言只允许它们。)

答案 3 :(得分:14)

你必须误解指针上下文中常数为零的含义。

在C和C ++指针中都没有“零值”。指针不是算术对象。他们选择具有“零”或“负”等数值或任何具有该性质的数值。因此,关于“指针......具有零值”的陈述根本没有意义。

在C& C ++指针可以具有保留的空指针值。空指针值的实际表示与任何“零”无关。它绝对适合给定平台。确实,在大多数平台上,空指针值在物理上由实际的零地址值表示。但是,如果在某个平台上,地址0实际上用于某种目的(即您可能需要在地址0处创建对象),则此类平台上的空指针值很可能会有所不同。例如,它可以物理地表示为0xFFFFFFFF地址值或0xBAADBAAD地址值。

然而,无论在给定平台上如何呈现空指针值,在代码中您仍将继续通过常量0指定空指针。为了将空指针值分配给给定指针,您将继续使用p = 0之类的表达式。编译器有责任实现您想要的并将其转换为正确的空指针值表示,即将其转换为将0xFFFFFFFF的地址值放入指针p的代码,例如。

简而言之,在您的代码中使用0生成空指针值的事实并不意味着空指针值以某种方式与地址0相关联。您在源代码中使用的0只是“语法糖”,它与空指针值“指向”的实际物理地址完全无关。

答案 4 :(得分:8)

  

但是,由于存储器寻址从0开始,并不像其他任何地址一样是有效地址吗?

在某些/多个/所有操作系统上,内存地址0在某种程度上是特殊的。例如,它经常映射到无效/不存在的内存,如果您尝试访问它会导致异常。

  

为什么不是负数而是null?

我认为指针值通常被视为无符号数:否则例如32位指针只能处理2 GB内存而不是4 GB。

答案 5 :(得分:4)

我的猜测是,选择了神奇值0来定义无效指针,因为它可以用较少的指令进行测试。一些机器语言在加载寄存器时会自动设置零和符号位,这样你就可以测试一个带有简单加载的空指针然后分支指令而不进行加载然后比较然后分支。

在作为我工作的第一台机器的Commodore Pet,Vic20和C64上,RAM从位置0开始,所以如果你真的想要使用空指针读取和写入是完全有效的。

答案 6 :(得分:3)

我认为这只是一个惯例。必须有一些值来标记无效指针。

你只丢失一个字节的地址空间,这应该很少有问题。

没有负面指针。指针始终未签名。此外,如果它们可能是否定的,那么您的约定就意味着您将失去一半的地址空间。

答案 7 :(得分:3)

虽然C使用0来表示空指针,但请记住指针本身的值可能不是零。但是,大多数程序员只会使用空指针实际为0的系统。

但为什么是零?嗯,这是每个系统共享的一个地址。并且通常低地址被保留用于操作系统目的,因此该值很好地适用于应用程序的禁止。意外地将整数值赋值给指针的可能性最终为零。

答案 8 :(得分:2)

许多操作系统对空指针表示使用all-bits-zero的一个重要原因是,这意味着memset(struct_with_pointers, 0, sizeof struct_with_pointers)类似地将struct_with_pointers内的所有指针设置为空指针。 C标准无法保证这一点,但很多很多程序都认为它。

答案 9 :(得分:2)

关于在删除指针后没有设置指针的参数,以便将来删除“暴露错误”......

如果您真的非常担心这一点,那么一个更好的方法,即保证可行的方法,就是利用assert():


...
assert(ptr && "You're deleting this pointer twice, look for a bug?");
delete ptr;
ptr = 0;
...

这需要一些额外的输入,并在调试版本中进行一次额外的检查,但它肯定会给你你想要的东西:当ptr被删除“两次”时注意。在评论讨论中给出的替代方案,没有将指针设置为null,因此您将崩溃,根本不能保证成功。更糟糕的是,与上述不同,如果其中一个“错误”进入架子,它可能会导致用户崩溃(或者更糟糕!)。最后,此版本允许您继续运行程序以查看实际发生的情况。

我意识到这并没有回答所提出的问题,但是我担心有人在阅读评论时可能会得出这样的结论:如果有可能将它们发送到免费,那么将指针设置为0被认为是“良好做法”。 ()或删除两次。在极少数情况下,使用未定义的行为作为调试工具绝对不是一个好习惯。没有人曾经不得不追捕最终因删除无效指针而引起的错误。这些类型的错误需要花费数小时才能完成,并且几乎总是会以一种完全出乎意料的方式影响程序,这种方式难以追溯到原来的问题。

答案 10 :(得分:2)

历史上,应用程序的低内存被系统资源占用。在那些日子里,零成为默认的空值。

虽然对于现代系统来说不一定如此,但是将指针值设置为除了内存分配给你的任何东西仍然是一个坏主意。

答案 11 :(得分:1)

Sentinel值的选择是任意的,实际上这是由下一版本的C ++(非正式地称为“C ++ 0x”,最有可能在将来称为ISO C ++ 2011)中解决的。引入关键字nullptr来表示空值指针。在C ++中,值0可以用作任何POD和具有默认构造函数的任何对象的初始化表达式,并且它具有在指针初始化的情况下分配sentinel值的特殊含义。至于为什么没有选择负值,对于某些值N,地址通常在0到2 N -1的范围内。换句话说,地址通常被视为无符号值。如果最大值被用作标记值,则它必须根据存储器的大小而在系统之间变化,而0始终是可表示的地址。它也用于历史原因,因为内存地址0通常在程序中不可用,而现在大多数操作系统都将内核的一部分加载到内存的较低页面中,并且这些页面通常以这样的方式受到保护:通过程序触摸(取消引用)(保存内核)将导致错误。

答案 12 :(得分:1)

它必须有一些价值。显然,您不希望踩到用户可能合法想要使用的值。我推测,由于C运行时为零初始化数据提供了BSS段,因此将零解释为未初始化的指针值具有一定的意义。

答案 13 :(得分:1)

在其中一台旧的DEC机器(PDP-8,我认为)中,C运行时将内存保护内存的第一页,以便任何访问该块中的内存的尝试都会引发异常。

答案 14 :(得分:0)

0是一个特殊值,在特定表达式中具有各种含义。在指针的情况下,正如已多次指出的那样,它的使用可能是因为当时这是说“在此处插入默认的哨兵值”的最方便的方式。作为常量表达式,它与指针表达式的上下文中的按位零(即,所有位设置为零)具有相同的含义。在C ++中,有几种类型没有NULL的按位零表示,例如指针成员和指向成员函数的指针。

值得庆幸的是,C ++ 0x有一个新的关键字“表达式,这意味着一个已知的无效指针也不会映射到积分表达式的按位零”:nullptr。虽然有一些系统可以使用C ++进行定位,允许在没有barfing的情况下取消引用地址0,但程序员要小心。

答案 15 :(得分:0)

操作系统很少允许您写入地址0.通常在低内存中粘贴特定于操作系统的内容;即IDT,页表等(这些表必须在RAM中,并且更容易将它们粘在底部而不是尝试确定RAM顶部的位置。)并且没有正确思维的操作系统会让你willy-nilly编辑系统表。

这可能不是K& R在制作C时的想法,但它(以及0 == null非常容易记住的事实)使0成为一个受欢迎的选择。

答案 16 :(得分:0)

这个帖子已经有很多好的答案了;可能有很多不同的原因让值0更喜欢空指针,但我还要再增加两个:

  • 在C ++中,对指针进行零初始化会将其设置为null。
  • 在许多处理器上,将值设置为0或测试等于/不等于0的效率比任何其他常量更高效。

答案 17 :(得分:0)

这取决于C / C ++中指针的实现。在指针的赋值中,NULL没有特定的原因。

答案 18 :(得分:-1)

这有历史原因,但也有优化原因。

操作系统通常提供一个进程,其内存页面初始化为0.如果程序想要将该内存页面的一部分解释为指针,则它为0,因此程序很容易确定该指针未初始化。 (当应用于未初始化的闪存页面时,这不能很好地工作)

另一个原因是在很多处理器上很容易将值的等价值测试为0.有时候,在没有任何额外指令的情况下进行自由比较,通常可以在不需要提供零值的情况下完成。另一个寄存器或指令流中的文字作为比较的。

大多数处理器的廉价比较是小于0的符号,并且等于0.(这两者都隐含了大于0且不等于0的符号)

由于所有可能值中的1个值需要保留为坏或未初始化,因此您可以将其作为具有与坏值等效的最便宜测试的值。对于'\ 0'终止的字符串也是如此。

如果你为了这个目的而尝试使用大于或小于0的话,你最终会将你的地址范围缩小一半。

答案 19 :(得分:-2)

使用常量0代替NULL,因为C是由数万年前的一些穴居人NULLNILZIP制作的,或者NADDA会比0更有意义。

  

但是因为内存寻址始于   0,不是0就像有效地址一样   还有其他吗?

事实上。虽然很多操作系统都不允许你在地址零处映射任何东西,即使在虚拟地址空间中(人们意识到C是一种不安全的语言,并且反映出空指针解引用错误很常见,所以决定通过不使用它来“修复”它们。用户空间代码映射到第0页;因此,如果您调用回调但回调指针为NULL,则不会最终执行某些任意代码。)

  

0如何用于处理null   指针,如果是这样的话?

因为与指针比较使用的0将替换为某些实现特定的值,这是malloc失败时malloc的返回值。

  

为什么负数不为空   代替?

这会更令人困惑。

答案 20 :(得分:-5)

请在阅读本文之前阅读本段。 我要求任何有兴趣阅读这篇文章的人都应该仔细阅读,当然不要#39; t downvote it,直到你完全理解它为止。

现在是社区维基,如果有人不同意任何概念,请修改它,并清楚详细地解释错误的原因和原因,如果可能请引用来源或提供可以转载的证据

<强> 答案

以下是一些可能是NULL == 0

的潜在因素的其他原因
  1. 零为假的事实,因此可以直接if(!my_ptr)代替if(my_ptr==NULL)
  2. 默认情况下,未启动的全局整数初始化为全零,并且所有零的指针都将被视为未初始化。
  3. 我想在其他答案上说一句话

    不是因为语法糖

    因为语法糖而说NULL是零,所以没有太大意义,如果是这样的话为什么不使用数组的索引0来保持它的长度?

    事实上,C语言是最接近内部实现的语言,C语言因为语法糖而选择为零是否有意义?他们宁愿提供一个关键字null(与许多其他语言一样),而不是将零映射为NULL!

    因此,虽然今天它可能只是语法糖,很明显C语言开发人员的初衷并不是语法糖,我将进一步展示。

    1) 规范

    然而,虽然C规范确实从常量0作为空指针(第6.3.2.3节),并且还定义了要实现定义的NULL(C11规范中的7.19节和C99规范中的7.17节) ),事实上仍然存在于“C编程语言”一书中。由C的发明人撰写,以下在5.4节中说明:

      

    C保证零永远不是数据的有效地址,因此返回值零可用于发出异常事件的信号,在这种情况下,没有空格。

         

    指针和整数不可互换,零是唯一的例外:常量零可以分配给指针,指针可以与常数零进行比较。符号常量NULL通常用于代替零,作为助记符更清楚地表明这是指针的特殊值。 NULL定义于。我们今后将使用NULL。

    正如人们可以看到的那样(从单词&#34;零地址&#34;)至少C的作者的初衷是地址为零,而不是常数为零,而且从这个摘录中可以看出规范从常量零开始说明的原因可能不是排除一个求值为零的表达式,而是将整数常量0包括为允许在不进行转换的指针上下文中使用的唯一整数常量。

    2) 摘要

    虽然规范没有明确说明零地址可以被处理为与零常数不同,但它并没有说不是,并且处理空指针常量时的事实并不声称它是由 NULL 定义的常量定义的实现,而是声称它为零,表明零常量和零地址之间可能存在差异。

    (但是如果是这种情况我只是想知道为什么NULL是实现定义的,因为在这种情况下NULL也可以是常数零,因为编译器无论如何必须将所有零常量转换为实际的实现定义为NULL?)

    但是我没有在实际操作中看到这一点,并且在一般平台中,地址零和常数零被视为相同,并抛出相同的错误消息。

    此外,事实是今天的操作系统实际上是保留整个第一页(范围0x0000到0xFFFF),只是为了防止因为C的空指针而访问零地址(参见{ {3}},以及由Jeffrey Richter和Christophe Nasarre撰写的Windows#C ++ C ++(由Microsoft Press出版)&#34;)。

    因此,我会向任何声称实际上已经看到它的人请求,请指定平台,编译器以及他实际执行的确切代码(尽管由于规范中的模糊定义[如我所示]任何编译器和平台都可以自由地做任何他想做的事。)

    然而,显然C的作者似乎没有想到这一点,而且他们说的是“零地址”,并且“C”保证它永远不会一个有效的地址&#34;,以及&#34; NULL只是一个助记符&#34;,清楚地表明它的初衷并非用于&#34;语法糖&#34;。

    不是因为操作系统

    还声称操作系统拒绝访问地址为零,原因如下:

    1)当写下C时没有这样的限制,正如人们可以在这个wikipage http://en.wikipedia.org/wiki/Zero_page上看到的那样。

    2)事实是C编译器确实访问了内存地址为零。

    这似乎是BellLabs(http://en.wikipedia.org/wiki/Zero_page

    的以下论文中的事实
      

    这两个编制者在如何处理这个问题上的细节不同。在前面的一个中,通过命名一个函数找到了开始;在后面,开始只是为0.这表明第一个编译器是在我们有一台带内存映射的机器之前编写的,所以程序的原点不在0的位置,而在第二个时间,我们有一个确实提供映射的PDP-11。

    (事实上截至今天(正如我上面引用维基百科和微软出版社的参考资料),限制访问零地址的原因是因为C的空指针!所以最后它结果是反过来!)

    3)请记住,C也用于编写操作系统,甚至是C编译器!

    事实上,C是为了用它编写UNIX操作系统而开发的,因此它似乎没有理由将它们限制在零地址之外。

    (硬件)有关计算机如何(物理上)能够访问地址零的说明

    我想在此解释另一点,如何可以引用地址零?

    想一想,地址由处理器提取,然后作为电压发送到存储器总线上,然后由存储器系统用来到达实际地址,但地址为零将意味着没有电压,那么内存系统的物理硬件如何访问地址为零?

    答案似乎是,地址零是默认值,换句话说,当内存总线完全关闭时,内存系统总是可以访问地址零,因此任何读取或写入请求都没有指定实际地址(地址为零的情况)自动访问地址零。