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

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

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

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

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

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

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

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

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

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

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

没有要求空指针等于0地址,只是大多数编译器以这种方式实现它。 完全可以通过存储一些其他值来实现空指针,实际上一些系统会这样做 。 C99规范 §6.3.2.3(指针)仅指定值为0的整型常量表达式是空指针常量,但它并未说明转换为整数时空指针的值为0。

值为0的整型常量表达式或类型为void *的表达式称为空指针常量。

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

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

零地址和NULL指针不是(必然)相同的东西。 只有文字零是空指针。 换一种说法:

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

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

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

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

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

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

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

如果您感到好奇,请阅读虚拟内存以进行更多参与讨论。

我没有看到答案直接解决我认为你问的问题,所以这里有:

是的,由于用于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

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

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

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

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

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

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