为什么地址零用于空指针?
在C(或C ++)中,如果指针的值为零,指针是特殊的:我build议在释放内存之后将指针设置为零,因为这意味着再次释放指针并不危险。 当我调用malloc的时候,如果它不能得到我的内存,它会返回一个零值的指针; 我一直使用if (p != 0)
来确保传入的指针是有效的,等等。
但是由于内存寻址从0开始,不是0就像其他地址一样是有效地址吗? 如果是这种情况,怎么能用0来处理空指针呢? 为什么不是一个负数,而是null?
编辑:
一堆很好的答案。 我将总结一下自己心中所解释的答案,并希望如果我误解,社区会纠正我。
-
像编程中的其他所有东西一样,它是一个抽象。 只是一个常量,与地址0无关.C ++ 0x通过添加关键字
nullptr
强调这一点。 -
它甚至不是地址抽象,它是C标准指定的常量,编译器可以将其转换为其他数字,只要它确保它永远不等于“真实”地址,并且等于其他空指针(如果0不是为平台使用最好的价值。
-
如果它不是抽象的,那么早期就是这种情况,地址0被系统使用,并被程序员禁止。
-
我承认,我的负面数字build议是一个小狂野的头脑风暴。 对地址使用带符号的整数有点浪费,如果它意味着除了空指针(-1或任何)之外,值空间在正整数之间平均分配,使有效地址和负数被浪费。
-
如果任何数字总是可以用数据types来表示的话,那么它就是0(也可能是1),我认为一位整数是0或者1,如果是无符号的,或者只是有符号的位,如果是有符号的,会是[-2,1],但是你可以将0设为空,1是内存中唯一可访问的字节。
仍然有一些在我脑海中没有解决的问题。 堆栈溢出问题指向特定的固定地址的指针告诉我,即使0为空指针是一个抽象,其他指针值也不一定。 这导致我发布另一个堆栈溢出的问题, 我可曾想要访问地址零? 。
2分:
-
只有源代码中的常数值0是空指针 – 编译器实现可以使用任何它想要或在运行代码中需要的值。 有些平台有一个特殊的指针值,这个值是'无效的',实现可能用作空指针。 C常见问题有一个问题, “真的,有真正的机器真的使用非零空指针,或指针不同types的不同表示? ,指出了几个使用这个属性的平台,它们是C源代码中的空指针,而在运行时则performance得不同。 C ++标准有一个注意事项,清楚地表明将“一个整型常量expression式转换为零值总是产生一个空指针,但是转换碰巧具有零值的其他expression式则不需要产生一个空指针”。
-
一个负值可能和平台一样可以用作地址 – C标准只需要select一些东西来表示一个空指针,并且select了零。 我真的不确定是否考虑了其他的哨兵价值。
空指针的唯一要求是:
- 它保证比较不等于指向实际对象的指针
- 任何两个空指针会比较相等(C ++提高了这一点,这只需要指针指向相同的types)
历史上,从0开始的地址空间总是ROM,用于某些操作系统或低级中断处理例程,现在由于一切都是虚拟的(包括地址空间),操作系统可以将任何分配映射到任何地址,所以它可以特别是不分配任何地址0。
IIRC,“空指针”值不保证为零。 编译器将0转换为适合系统的“null”值(实际上可能总是为零,但不一定)。 每当您将指针与零比较时,都会应用相同的翻译。 因为你只能比较指针和对这个特殊值0的指针,所以程序员不会知道系统的内存表示。 至于为什么他们select了0而不是42或者这样的话,我会猜测这是因为大多数程序员从0开始计数:)(另外,在大多数系统中,0是第一个内存地址,他们希望它方便,因为在练习翻译,就像我所描述的很less发生,语言只是允许他们)。
你必须误解指针上下文中常量零的含义。
C和C ++指针都不能“有零值”。 指针不是算术对象。 它们具有诸如“零”或“负”或任何这种性质的数值。 所以你对“指针……有零值”的陈述根本没有意义。
在C&C ++指针中可以有保留的空指针值 。 空指针值的实际表示与任何“零”无关。 对于给定的平台,它可以是绝对适合的。 确实,在大多数平面上,空指针值由实际的零地址值来物理地表示。 但是,如果在某些平台上地址0实际上用于某种目的(即,您可能需要在地址0创build对象),那么这种平台上的空指针值很可能是不同的。 例如,它可以物理地表示为0xFFFFFFFF
地址值或者作为0xBAADBAAD
地址值。
尽pipe如此,无论在给定的平台上如何呈现空指针值,在您的代码中,您仍然会继续用常量0
指定空指针。 为了给给定的指针分配一个空指针值,你将继续使用像p = 0
这样的expression式。 编译器有责任实现你想要的东西,并将其翻译成适当的空指针值表示,即将其翻译成将地址值0xFFFFFFFF
置于指针p
的代码。
简而言之,在你的sorce代码中使用0
来生成空指针值这一事实并不意味着空指针值在某种程度上与地址0
。 你在源代码中使用的0
只是“语法糖”,与空指针值“指向”的实际物理地址绝对没有关系。
但是由于内存寻址从0开始,不是0就像其他地址一样是有效地址吗?
在某些/许多/所有操作系统上,内存地址0在某些方面是特殊的。 例如,它通常映射到无效/不存在的内存,如果您尝试访问该内存,则会导致exception。
为什么不是一个负数,而是null?
我认为指针值通常被视为无符号数字:否则例如一个32位指针只能够处理2 GB的内存,而不是4 GB。
我的猜测是魔法值0被选中来定义一个无效指针,因为它可以用较less的指令进行testing。 一些机器语言在加载寄存器的时候会自动设置零和符号位,这样你就可以用一个简单的负载testing一个空指针,然后在没有执行加载的情况下分支指令,然后比较然后分支。
在第一台机器上的Commodore Pet,Vic20和C64上,RAM从0开始,所以如果你真的想用空指针来读写,那是完全有效的。
我认为这只是一个惯例。 必须有一些值来标记一个无效的指针。
你只是失去了一个字节的地址空间,这应该很less成为一个问题。
没有负面的指针。 指针始终未签名。 此外,如果他们可能是负面的,你的约定意味着你失去了一半的地址空间。
历史上,应用程序的低内存被系统资源占用。 在那些日子里,零成了默认的空值。
虽然这对于现代系统来说不一定是正确的,但是将指针值设置为任何东西,但是分配给你的内存是一个坏主意。
尽pipeC使用0来表示空指针,但请记住,指针本身的值可能不是零。 但是,大多数程序员只会使用空指针的系统,实际上是0。
但为什么零? 那么,这是每个系统共享的地址。 低地址通常保留用于操作系统的目的,因此该值适用于应用程序的禁止。 将整数值偶然分配给指针的可能性最终为零。
关于在删除它之后没有把指针设置为空的参数,以便将来删除“暴露错误”…
如果你确实真的担心这个问题,那么一个更好的方法就是利用assert():
... assert(ptr && "You're deleting this pointer twice, look for a bug?"); delete ptr; ptr = 0; ...
这需要一些额外的input,并在debugging版本中进行一次额外的检查,但是一定会给你想要的东西:注意当ptr被删除两次。 在评论讨论中给出的替代方法,不是将指针设置为null,所以你会得到一个崩溃,不能保证成功。 更糟糕的是,与上述不同的是,如果这些“错误”中的一个进入到货架上,则可能导致用户崩溃(或者更糟糕!)。 最后,这个版本让你继续运行程序,看看实际发生了什么。
我意识到这并不回答问题,但我担心有人阅读评论可能会得出这样的结论:如果可能将它们发送到free()或删除两次。 在less数情况下,使用未定义行为作为debugging工具不是一个好习惯。 没有人不得不寻找一个最终由于删除一个无效指针而导致的错误。 这些错误需要数小时的时间才能以完全意想不到的方式追捕和近乎完成,难以追溯到原来的问题。
在其中一台旧的DEC机器(PDP-8,我认为)中,C运行时将内存保护内存的第一页,以便任何尝试访问该块中的内存都会引发exception。
哨兵值的select是任意的,事实上,下一版本的C ++(非正式地称为“C ++ 0x”,最有可能在未来被称为ISO C ++ 2011)正在解决这个问题。关键字nullptr
表示空值指针。 在C ++中,可以使用值0作为任何POD和具有默认构造函数的任何对象的初始化expression式,并且在指针初始化的情况下具有指定标识值的特殊含义。 至于为什么没有select负值,对于某个值N,地址通常为0到2 N -1。换句话说,地址通常被视为无符号值。 如果最大值被用作标记值,则根据存储器的大小,它将不得不根据系统而变化,而0总是可表示的地址。 它也用于历史原因,因为内存地址0通常在程序中是不可用的,而且现在大多数操作系统都将内核的一部分加载到较低页面的内存中,并且这样的页面通常以这样的方式受到保护:如果通过程序(保存内核)触摸(取消引用)会导致错误。
它必须有一定的价值。 显然,你不想踩在用户可能合法想要使用的值。 我会推测,由于C运行时提供了零初始化数据的BSS段,因此将零解释为未初始化的指针值是有一定意义的。
很less有操作系统允许你写地址0.通常在低内存的操作系统下, 即IDT,页表等(这些表格必须在RAM中,而且更容易将它们粘在底部,而不是试图确定RAM的顶端在哪里)。没有任何操作系统在正确的意识中会让你编辑系统表willy-nilly。
这可能没有在K&R的脑海里,当他们做C,但它(以及0 == null的事实很容易记住)使0成为一个stream行的select。
值0
是在特定expression式中具有各种含义的特殊值。 在指针的情况下,正如已经多次指出的那样,这可能是因为当时它是“在这里插入默认定位值”的最方便的方式。 作为一个常量expression式,它在指针expression式的上下文中不具有与按位0相同的含义(即所有位设置为零)。 在C ++中,有几种types不具有NULL
的按位零表示,例如指针成员和指向成员函数的指针。
谢天谢地,C ++ 0x有一个新的关键字“expression式,这意味着一个已知的无效指针,也不映射到整数expression式按位零”: nullptr
。 虽然有几个系统可以用C ++作为目标,而不需要禁止引用地址0,所以程序员要小心。
在这个线程中已经有很多很好的答案。 可能有许多不同的原因,优先selectnull指针的值为0
,但是我要添加两个:
- 在C ++中,零初始化指针会将其设置为空。
- 在许多处理器上,将一个值设置为0或者对其进行等于/不等于0的testing比对于任何其他常量更有效。
这取决于C / C ++中指针的实现。 为什么NULL在指针的赋值中没有任何具体的原因。
许多操作系统为空指针表示使用全零位的一个重要原因是这意味着memset(struct_with_pointers, 0, sizeof struct_with_pointers)
以及类似的操作会将memset(struct_with_pointers, 0, sizeof struct_with_pointers)
所有指针struct_with_pointers
为空指针。 这不是C标准所能保证的,但许多程序都假设它。
这有一个历史的原因,但也有优化的原因。
对于操作系统来说,提供一个初始化为0的内存页的进程是很常见的。如果一个程序想把该内存页的一部分解释为一个指针,那么它就是0,所以程序很容易确定这个指针是未初始化。 (这在应用于未初始化的Flash页面时效果不佳)
另一个原因是,在许多处理器上,testing一个值等于0是非常容易的。它有时是一个免费的比较,没有任何额外的指令需要,通常可以不需要在另一个寄存器中提供一个零值作为指令stream中的文字进行比较。
大多数处理器的廉价比较是有符号小于0,并且等于0.(两者均暗示符号大于0且不等于0)
由于所有可能的值中的1个值需要被保留为坏的或未初始化的,所以您可以使其成为具有与坏值等价的最便宜的testing的值。 对于'\ 0'终止的string也是如此。
如果你为了这个目的而试图使用大于或小于0的值,那么你最终会把你的地址范围减半。
常量0
被用来代替NULL
因为C是由几年前的几个穴居人制作的, NULL
, NIL
, ZIP
或NADDA
都会比0
更有意义。
但是由于内存寻址从0开始,不是0就像其他地址一样是有效地址吗?
确实。 尽pipe许多操作系统都不允许你在地址0映射任何东西,甚至在虚拟地址空间(人们意识到C是一种不安全的语言,并且反映出空指针解引用错误是非常普遍的,但是决定通过忽略用户空间代码映射到页面0;因此,如果您调用callback,但callback指针为NULL,则不会执行一些任意代码)。
如果是这种情况,怎么能用0来处理空指针呢?
因为用于比较指针的0
将被replace为某个实现特定的值,这是malloc在malloc失败时的返回值。
为什么不是一个负数,而是null?
这会更混乱。
( 请在阅读post前仔细阅读本段文字,我希望任何有兴趣阅读本文的人都应该仔细阅读,当然,除非完全理解,否则不要低估它,谢谢。)
现在是社区维基,如果有人不同意任何概念,请修改它,明确和详细地解释什么是错的,为什么,如果可能请引用消息来源或提供证据可以复制。
回答
以下是可能成为NULL == 0的基本因素的其他一些原因
- 零是假的事实,所以可以直接做
if(!my_ptr)
而不是if(my_ptr==NULL)
。 - 事实上,未初始化的全局整数默认初始化为全零,因此全零的指针将被视为未初始化的。
在这里,我想在其他答案上说一个字
不是因为语法糖
由于语法糖来说NULL是零,没有太大意义,如果这样的话为什么不用一个数组的索引0来保存它的长度呢?
In fact C is the language that most closely resembles the internal implementation, does it make sense to say that C picked zero just because of syntactic sugar? They would rather provide a keyword null (as many other languages do) rather than mapping zero to NULL!
As such while as of today it might just syntactic sugar, it is clear that the original intention of the C language developers was not for syntactic sugar, as I will show further.
1) The Specification
Yet while it is true that the C specification speak from the constant 0 as the null pointer (section 6.3.2.3), and also define NULL to be implementation defined (section 7.19 in the C11 specification, and 7.17 in the C99 specification), the fact remains that in the book "The C Programming Language" written by the inventors of C the following is stated in section 5.4:
C guarantees that zero is never a valid address for data, so a return value of zero can be used to signal an abnormal event, in this case, no space.
Pointer and integers are not interchangeable, Zero is the sole exception: the constant zero may be assigned to a pointer, and a pointer may be compared with the constant zero. The symbolic constant NULL is often used in place of zero, as a mnemonic to indicate more clearly that this is a special value for a pointer. NULL is defined in . We will use NULL henceforth.
As one can see (from the words "zero address") at least the original intention of the authors of C were of the address zero, and not the constant zero, moreover it appears from this excerpt that the reason why the specification speaks from the constant zero is probably not to exclude an expression that evaluates to zero, but instead to include the integer constant zero to be the only integer constant allowed for use in a pointer context without casting.
2) Summary
While the specification does not say explicitly that a zero address can be treated different than the zero constant, it does not say that not, and the fact the when dealing with the null-pointer constant it does not claim it to be implementation defined as it does by the NULL defined constant, instead claim it to be zero, shows that there might be a difference between the zero constant and the zero address.
(However if this is the case I just wonder why NULL is implementation defined, since in such a case NULL can also be the constant zero, as the compiler anyway has to convert all zero constants into the actual implementation defined NULL?)
However I don not see this in real action, and in the general platforms the address zero and the constant zero are treated the same, and throw the same error message.
Furthermore the fact is that today's operating systems are actually reserving the entire first page (range 0x0000 to 0xFFFF), just to prevent access to the zero address because of C's NULL pointer, (see http://en.wikipedia.org/wiki/Zero_page , as well as "Windows Via C/C++ by Jeffrey Richter and Christophe Nasarre (published by Microsoft Press)").
Thus I would ask from anyone claiming to actually have it seen in action, to please specify the platform, and compiler, and the exact code he actually did, (although due to the vague definition in the specification [as I have shown] any compiler and platform is free to do whatever he wants).
However it apparently seems that the authors of C didn't had this in mind, and they were speaking of the "zero address", and that "C guarantees that it is never a valid address", as well as "NULL is just a mnemonic", clearly showing that it's original intention was not for "syntactic sugar".
Not Because Of The Operating System
Also claiming that the operating system denies access to address zero, for a few reasons:
1) When C was written there was no such restriction, as one can see on this wikipage http://en.wikipedia.org/wiki/Zero_page .
2) The fact is that C compilers did accessed memory address zero.
This appears to be the fact from the following paper by BellLabs ( http://www.cs.bell-labs.com/who/dmr/primevalC.html )
The two compilers differ in the details in how they cope with this. In the earlier one, the start is found by naming a function; in the later, the start is simply taken to be 0. This indicates that the first compiler was written before we had a machine with memory mapping, so the origin of the program was not at location 0, whereas by the time of the second, we had a PDP-11 that did provide mapping.
(In fact as of today (as I cited references above from wikipedia and microsoft press), the reason for restricting access to the zero address is because of C's NULL pointers! So at the end it turns out to be the other way around!)
3) Remember that C is also used to write operating systems, and even C compilers!
In fact C was developed for the purpose of writing the UNIX operating system with it, and as such it appears to be no reason why they should restrict themselves from address zero.
(Hardware) Explanation On How Computers Are (Physically) Able To Access Address Zero
There is another point I want to explain here, how is it possible to reference address zero at all?
Think of it for a second, the addresses are fetched by the processor, and then sent as voltages on the memory bus, which is then used by the memory system to get to the actual address, and yet a address of zero will mean no voltage, so how is the physical hardware of the memory system accessing address zero?
The answer appears to be, that address zero is the default, and in other words address zero is always accessible by the memory system when the memory bus is completly off, and as such any request to read or write without specifying an actual address (which is the case with address zero) is automatically accessing address zero.