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

Joe*_*oel 119 c c++ memory pointers

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

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


编辑:

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

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

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

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

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

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

还有一些东西在我的脑海里没有得到解决.Stack Overflow问题指向特定固定地址的指针告诉我,即使0表示空指针是抽象,其他指针值也不一定.这导致我发布另一个Stack Overflow问题,我是否可以访问地址零?.

Mic*_*urr 63

2分:

  • 只有源代码中的常量值0是空指针 - 编译器实现可以使用它在运行代码中想要或需要的任何值.某些平台有一个特殊的指针值,该值是"无效的",实现可能会将其用作空指针.C FAQ有一个问题,"说真的,有任何实际的机器真的使用非零空指针,或指向不同类型的指针的不同表示吗?" ,这指出了几个使用0属性的平台是C源代码中的空指针,而在运行时则表示不同.C++标准有一个注释,清楚地表明转换"一个值为零的整数常量表达式总是产生一个空指针,但转换其他碰巧有零值的表达式不需要产生一个空指针".

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

空指针的唯一要求是:

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

  • +1我怀疑0只是出于历史原因而被选中.(0是一个起始和无效的地址,大部分时间.)当然,一般来说,这样的假设并不总是正确的,但0效果很好. (12认同)
  • Minix 16位编译器使用0xFFFF表示NULL. (10认同)
  • 空间也可能是一个促成因素.在C首次开发的时代,内存比现在更加昂贵.可以使用XOR指令方便地计算数字零,或者无需加载立即值.根据架构,这可能会节省空间. (8认同)
  • @GMan - 你是对的.在早期的CPU上,内存地址为零是特殊的,并且具有防止来自运行软件的访问的硬件保护(在某些情况下,它是复位向量的开始,并且修改它可以防止CPU重置或启动).程序员在其软件中使用此硬件保护作为错误检测的一种形式,让CPU的地址解码逻辑检查未初始化或无效的指针,而不必花费CPU指令来执行此操作.即使零地址的目的可能已经改变,该公约至今仍然存在. (6认同)
  • 在许多嵌入式系统中,0是有效地址.值-1(所有位1)也是有效地址.当数据从地址0开始时,很难计算ROM的校验和.:-( (2认同)
  • 在一些机器中,测试0比测试其他值更快.(操作后测试零标志)这也可能是一个促成因素. (2认同)

Avi*_* P. 31

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

  • 这就是它.按历史惯例,第一个地址用于中断处理程序,因此无法用于普通程序.此外,0为"空",可以解释为无值/无指针. (6认同)

rme*_*dor 15

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

  • @Justin:你误会了.常量0是*总是*空指针.@meador所说的是,空指针(由常数0表示)可能与地址零不对应.在某些平台上,创建空指针(`int*p = 0`)可能会创建一个包含值"0xdeadbeef"或其更喜欢的任何其他值的指针.0是空指针,但空指针不一定是指向零地址的指针.:) (5认同)
  • 但@Jalf,常量0*不是*总是空指针.当我们希望编译器为我们填充平台的*actual*null指针时,这就是我们编写的内容.实际上,空指针通常*确实*对应于地址零,但我将Joel的问题解释为问为什么会这样.毕竟,该地址应该有一个有效的内存字节,那么为什么不使用不存在的字节的不存在地址而不是从播放中删除有效字节?(我正在写我想象的乔尔在想什么,而不是我问自己的问题.) (3认同)

AnT*_*AnT 15

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

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

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

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

简而言之,您0在自己的代码中使用生成空指针值的事实并不意味着空指针值以某种方式与地址相关联0.将0您在源代码中使用的仅仅是"语法糖"是绝对没有关系的空指针值"指点"实际的物理地址.

  • @Ben Voigt:语言规范定义了*算术类型*的概念.我所说的只是指针类型不属于算术类型的类别.*指针算术*是一个与众不同且完全无关的故事,仅仅是一种语言巧合. (5认同)
  • <quote>指针不是算术对象</ quote>指针运算在C和C++中已经很好地定义了.部分要求是两个指针都指向同一个复合材料.空指针不指向任何复合,因此在指针算术表达式中使用它是非法的.例如,不能保证`(p1 - nullptr) - (p2 - nullptr)==(p1 - p2)`. (3认同)

Chr*_*isW 8

但是,由于内存寻址从0开始,因此有效地址不是0吗?

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

为什么不是负数而是null?

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

  • 我在一个设备上编码,其中地址零是有效地址,并且没有内存保护.空指针也是全位零; 如果您不小心写入了空指针,那么您就会对位于零地址的操作系统设置进行抨击; 通常没有接受欢闹. (4认同)
  • 是的:例如,在非保护模式 x86 CPU 上,地址 0 是 [中断向量表](http://wiki.osdev.org/Memory_Map_(x86))。 (2认同)

KPe*_*xEA 5

我的猜测是选择了魔术值0来定义无效的指针,因为可以用更少的指令来测试它。某些机器语言在加载寄存器时会根据数据自动设置零和符号标志,因此您可以通过简单的加载然后测试分支指令来测试空指针,而无需执行单独的比较指令。

(尽管大多数ISA仅在ALU指令上设置标志,但未设置标志。通常,您不会通过计算生成指针,除非在解析C 源代码时在编译器中。但是至少您不需要任意的指针宽度常量来比较。)

在我最初使用的Commodore Pet,Vic20和C64机器上,RAM从位置0开始,因此如果您确实愿意,使用空指针进行读取和写入完全有效。