为什么NULL / 0是对象的非法内存位置?

时间:2010-06-02 18:36:36

标签: c++ c null memory-management

我理解C / C ++中NULL常量的用途,我理解它需要在内部用某种方式表示。

我的问题是:对于C / C ++中的对象,0地址是否是无效的内存位置有一些根本原因吗?或者我们在理论上“浪费”由于此预留而占用一个字节的内存?

12 个答案:

答案 0 :(得分:21)

空指针实际上不必为0.在C规范中保证当在指针的上下文中给出常量0值时,编译器将其视为null,但是如果你这样做

char *foo = (void *)1;
--foo;
// do something with foo

您将访问0地址,不一定是空指针。在大多数情况下,实际情况确实如此,但这不是必要的,所以我们不必浪费那个字节。虽然在较大的图片中,如果它不是0,它必须是某种东西,所以一个字节被浪费某处

编辑:由于评论中的混淆,编辑了NULL的使用。此外,这里的主要消息是“空指针!= 0,这里是一些C /伪代码,显示了我正在努力做的事情。”请不要尝试编译这个或担心类型是否合适;意思很清楚。

答案 1 :(得分:11)

这与浪费内存无关,而与内存组织有关。

当您使用内存空间时,您必须假设任何不直接“属于您”的内容都由整个系统共享或非法访问。如果您已经在堆栈上的某些内容的地址仍然在堆栈上,或者您已从动态内存分配器接收到并且尚未回收它,则地址“属于您”。一些操作系统调用也将为您提供法律区域。

在实际模式(例如DOS)的美好时光中,机器地址空间的所有开头都不是由用户程序编写的。其中一些甚至映射到I / O之类的东西。 例如,写入0xB800(相当低)的地址空间实际上会让你捕获屏幕!在地址0处没有放置任何东西,并且许多内存控制器不允许您访问它,因此它是NULL的绝佳选择。事实上,如果你尝试在那里写一些电脑上的内存控制器就会疯狂。

今天,操作系统通过虚拟地址空间保护您。但是,不允许任何进程访问未分配给它的地址。大多数地址甚至没有映射到实际的内存页面,因此访问它们将触发一般保护错误或操作系统中的等效操作。这就是为什么0不浪费 - 即使您机器上的所有进程“都有一个地址0”,如果他们试图访问它,它也不会映射到任何地方。

答案 2 :(得分:8)

没有要求空指针等于0地址,只是大多数编译器以这种方式实现它。通过存储一些其他值并实际上some systems do this来实现空指针是完全可能的。 C99 specification§6.3.2.3(指针)仅指定值为0的整型常量表达式是空指针常量,但它并不表示转换为整数时空指针的值为0。

  

一个整数常量表达式,其值为0,或者这样的表达式强制转换为类型   void *,称为空指针常量。

     

任何指针类型都可以转换为整数类型。除非事先指明,否则   结果是实现定义的。如果结果无法以整数类型表示,   行为未定义。结果不必在任何整数的值范围内   类型。

在某些嵌入式系统上,零内存地址用于可寻址的内容。

答案 3 :(得分:7)

零地址和NULL指针不一定(必然)相同。只有 literal 零是空指针。换句话说:

char* p = 0; // p is a null pointer

char* q = 1;
q--; // q is NOT necessarily a null pointer

系统可以以他们选择的任何方式在内部自由表示空指针,并且这种表示可能会或者可能不会通过使实际的0地址非法来“浪费”一个字节的内存。但是,需要编译器将 literal 零指针转换为系统的NULL内部表示形式。除了被赋予文字零之外,以某种方式指向零地址的指针不一定是空的。

现在,大多数系统都使用0表示NULL,但它们不必使用。

答案 4 :(得分:6)

它不一定是非法的内存位置。我通过解除引用零指针来存储数据......它发生的数据是一个中断向量,存储在位于零地址的向量处。

按照惯例,它通常不被应用程序代码使用,因为历史上很多系统都有从零开始的重要系统信息。它可能是引导ROM或向量表,甚至是未使用的地址空间。

答案 5 :(得分:4)

在许多处理器上,地址为0是复位向量,其中包含bootrom(PC上的BIOS),因此您不太可能在该物理地址处存储任何内容。在具有MMU和支持OS的处理器上,物理和逻辑地址地址不必相同,并且地址零可能不是执行进程上下文中的有效逻辑地址。

答案 6 :(得分:2)

NULL通常是零地址,但它是应用程序虚拟地址空间中的零地址。您在大多数现代操作系统中使用的虚拟地址与实际物理地址完全无关,操作系统从虚拟地址空间映射到您的物理地址。所以,不,让代表NULL的虚拟地址0不会浪费任何内存。

如果您感到好奇,请阅读virtual memory以获得更多参与讨论。

答案 7 :(得分:2)

我没有看到直接解决你所想的想法的答案,所以这里有:

是的,因为用于 null 的常量,至少有1个地址值被“浪费”(使其无法使用)。它是否在过程存储器的线性映射中映射到0是不相关的。

地址不会用于数据存储的原因是你需要空指针的特殊状态,以便能够与任何其他真实指针区分开来。就像ASCIIZ字符串(C-string,NUL-terminated)的情况一样,NUL字符被指定为字符串的结尾,不能在字符串中使用。你还能在里面用吗?是的,但是这将误导库函数,因为字符串结束。

我能想到我正在学习的LISP的至少一个实现,其中NIL(Lisp的null)不是0,也不是无效地址而是真实对象。原因非常聪明 - 标准要求CAR(NIL)= NIL和CDR(NIL)= NIL(注意:CAR(l)返回指向列表的头/第一个元素的指针,其中CDR(l)返回ptr到列表的尾部/其余部分。)。因此,不是在CAR和CDR中添加if-checks,而是指针是否为NIL - 这将减慢每个调用 - 它们只是分配了一个CONS(思考列表)并指定其头部和尾部指向自身。那里! - 这样CAR和CDR就能工作,并且内存中的地址不会被重用(因为它被设计为NIL的对象占用)

PS。我记得那么多年以前我读过一些与NULL有关的Lattice-C的错误 - 一定是在黑暗的MS-DOS分段时间,你在那里使用单独的代码段和数据段 - 所以我记得有一个问题是链接库中的第一个函数可能有地址0,因此指向它的指针将被视为无效,因为== NULL

答案 8 :(得分:1)

但是,由于现代操作系统可以将物理内存映射到逻辑内存地址(或更好:从386开始的现代CPU),甚至不会浪费单个字节。

答案 9 :(得分:1)

正如人们已经指出的那样,NULL指针的位表示不能与0值的位表示相同。虽然几乎在所有情况下(具有特殊地址的旧恐龙计算机都可以忽略)因为NULL指针也可以用作布尔值,并且通过使用整数(足够大小)来保存指针值,它更容易代表现代CPU的常见ISA。处理它的代码更直接,因此更不容易出错。

答案 10 :(得分:1)

您注意到0的地址空间不适用于您的程序是正确的。出于多种原因,各种系统都不认为这是您程序的有效地址空间。

允许使用任何有效地址将需要所有指针的空值标志。这将超过地址0处丢失的内存的开销。还需要额外的代码来检查并查看地址是否为空,浪费内存和处理器周期。

理想情况下,NULL指针使用的地址(通常为0)应该在访问时返回错误。 VAX / VMS从未将页面映射到地址0,因此遵循NULL指针会导致失败。

答案 11 :(得分:0)

该地址的内存保留供操作系统使用。 0 - 64k保留。 0用作特殊值,表示开发人员“不是有效地址”。