为什么NULL不是有效的内存地址?

时间:2018-07-07 11:56:21

标签: c null

这听起来像是一个愚蠢的问题,但是由于在C语言中,NULL的字面定义为

#define NULL 0

为什么它不能是有效的内存地址?为什么我不能取消引用,为什么任何数据都不可能位于内存地址0?

我敢肯定,答案是类似“内存的前n个字节始终由内核保留”之类的东西,但是我在互联网上找不到类似的东西。

我推理的另一部分是,这不是平台独立的吗?我不能发明一种新的体系结构,使进程可以访问内存地址0吗?

3 个答案:

答案 0 :(得分:4)

取消引用NULLundefined behavior。任何事情都可能发生,并且大多数时候坏事都会发生。因此scared

某些旧架构(VAX ...)允许您取消引用NULL

C11标准规范(读为n1570)不要求NULL指针全为零(请参见C FAQ Q5.17);它可能是别的东西,但它应该是一个永远无效的地址,因此就C11而言,不能由成功的malloc或操作者的地址(一元&)获得。但是这样做比较方便,实际上,大多数(但不是全部)C实现都这样做。

IIRC,在Linux上,您可能会用(void*)0 mmap(2)包含MAP_FIXED的页面,但是这样做是不明智的(例如,因为允许使用符合标准的优化编译器来优化取消引用) NULL)。

实际上,(void*)0不是有效地址(在具有某些MMU和虚拟内存且运行良好操作系统的普通处理器上!),因为可以方便地确定它是NULL,并且确保将其取消引用会带来segmentation fault是很方便的。但这不是C标准所必需的(今天在便宜的微控制器上是错误的)。

C实现必须提供某种表示NULL指针的方式(并确保它永远不会是某个有效位置的地址)。这甚至可以通过惯例来完成:提供完整的2 32 字节地址空间,但是保证从不使用地址0(或您为NULL分配的任何地址,也许是42!)

NULL碰巧无法使用时,细分错误不会捕获到细微的错误(因此C程序更难调试)。

  

我是否可以发明一种新的体系结构,使进程可以访问内存地址0?

您可以,但是您不想这样做(如果您关心提供任何符合标准的C实现)。您希望将地址0设为NULL。否则会使编写C编译器(和标准C库)变得更加困难。并且使该地址无效,以至于在取消引用时出现分段错误,从而使调试(以及使用C编码的用户的生活)更加容易。

如果您梦想着奇怪的体系结构,请阅读Lisp machines(以及Rekursiviapx 432),并查看Liam Proven在FOSDEM2018上的The circuit less traveled演讲。这确实很有启发性,而且很好。

答案 1 :(得分:2)

使地址零未映射,这样,如果您的程序尝试访问它,就会出现陷阱,这是许多操作系统提供的一种便利。 C标准不需要。

根据C标准:

  • NULL不是任何对象或函数的地址。 (具体来说,它要求NULL比较不等于指向任何对象或函数的指针。)
  • 如果您确实将*应用于NULL,则标准未定义结果行为。

这对您意味着什么,您可以使用NULL作为指示符,该指针未指向任何对象或函数。这是C标准为NULL提供的唯一目的-使用诸如if (p != NULL)…之类的测试。 C标准不保证如果*pp时使用NULL会发生陷阱。

换句话说,C标准不需要NULL提供任何陷印功能。它只是一个与任何实际指针都不同的值,只是提供一个指针值就意味着“不指向任何东西”。

通用操作系统通常会专门安排对地址零处的内存进行取消映射(并且其C实现将NULL定义为(void *) 0或类似的东西),以便在出现以下情况时发生陷阱:解引用空指针。当他们这样做时,他们扩展了C语言,超出了规范的要求。它们特意从进程的内存映射中排除地址零,以使这些陷阱起作用。

但是,C标准不需要此。 AC实现可以自由地将地址为零的内存映射为空,并且,如果将*应用于空指针,则可能存在数据,并且如果操作系统,则程序可以读取和/或写入该数据。已经允许了。完成此操作后,通常是在旨在在操作系统内核(例如设备驱动程序,内核扩展或内核本身),嵌入式系统或具有简单操作系统的其他专用系统中运行的代码中完成操作。

答案 2 :(得分:1)

空指针常量NULL)的值为0。空指针 value 可能不是0。在转换过程中,编译器会将空指针常量的出现替换为实际的空指针值。

NULL not 代表“地址0”;相反,它表示一个定义良好的 invalid 指针值,该值保证不会指向任何对象或函数,并且尝试取消引用无效的指针会导致未定义的行为。