为什么NULL不是由编译器预定义的

这个问题困扰了我一段时间。 我从来没有看到过NULL的不同定义,它始终是

#define NULL ((void *) 0) 

是否有任何体系结构,其中NULL定义不同,如果是这样,为什么编译器不为我们声明这个?

C 2011标准, 在线草案

6.3.2.3指针

3值为0的整型常量表达式或类型为void *的表达式称为空指针常量66)如果将空指针常量转换为指针类型,则保证将结果指针(称为空指针)与指向任何对象或函数的指针进行比较。


66)宏NULL (和其他头文件)中定义为空指针常量; 见7.19。

NULL 始终定义为零值常量表达式; 它可以是裸0,或0转换为void * ,或其他一些求值为0的积分表达式。​​就源代码而言, NULL将始终求值为0。

一旦代码被翻译,任何出现的空指针常量(0, NULL等)将被底层架构用于空指针的任何内容替换,空指针可能是也可能不是0值。

WhozCraig将这些评论写入一个现已删除的答案 ,但它可以提升为完整的答案(这就是我在这里所做的)。 他指出:

有趣的说明:AS / 400是一个非常独特的平台,任何非有效指针都被视为等效于NULL 。 他们用来做这件事的机制简直太棒了。 在这种意义上,“有效”是任何128位指针(平台使用128位线性地址空间用于所有内容 ),其中包含由已知可信指令集获得的“值”。 很难相信, int *p = (int *)1; if (p) { printf("foo"); } int *p = (int *)1; if (p) { printf("foo"); } int *p = (int *)1; if (p) { printf("foo"); }不会在该平台上打印“foo”。 分配给p的值不是源信任的,因此被认为是“无效”,因此等同于NULL

坦率地说它是如何运作的。 进程的映射虚拟地址空间中的每个16字节段落在进程范围的位图中具有相应的“位”。 所有指针必须位于其中一个段落边界上。 如果该位是“亮起”,则相应的指针存储在受信任的源中,否则它是无效的并且等效于NULL。 在确定该位是否点亮时,都会仔细检查对malloc,指针数学等的调用。 正如你可以想象的那样,在结构中加入指针会给结构包装的想法带来全新的伤害。


这是标记为社区维基(这不是我的答案 – 我不应该得到信用)但如果WhozCraig写下他自己的答案,它可以被删除。

这表明存在具有有趣指针属性的真实平台。

有些平台的#define NULL ((void *)0)不是通常的定义; 在某些平台上,只要编译器理解它,它就可以只是0 ,在其他平台上, 0L0ULL或其他适当的值。 C ++不喜欢((void *)0)作为定义; 标头与C ++交互的系统可能不会使用void指针版本。

我在一台机器上学习了C,其中给定内存位置的char *地址的表示与同一内存位置的int *地址不同。 这是在void *之前的几天,但这意味着你必须正确声明malloc()char *malloc(); – 也没有原型),你必须显式地将返回值强制转换为正确的类型或者你有核心转储。 感谢C标准(尽管有问题的机器,ICL Perq – 来自Three Rivers的标记硬件 – 在很大程度上取决于标准定义的时间)。

在ANSI-C之前的黑暗时代,旧的K&R C在硬件上有许多不同的实现,这些实现在今天被认为是奇怪的。 这是在机器非常“真实”的VM时代之前。 零地址不仅在这些机器上很好,零地址可能很受欢迎……我认为CDC有时将零系统常数存储为零(如果设置为非零,则会发生奇怪的事情)。

  if(NULL!= ptr)/ *喜欢这个* /
  if(ptr)/ *从不喜欢这个* / 

诀窍是找到你可以安全地用来表示“没有”的地址,因为在内存末端存储东西也很流行,这在一些架构上排除了0xFFFF。 这些架构倾向于使用字地址而不是字节地址。

我不知道答案,但我在猜测。 在C中,你通常会做很多mallocs,因此对返回的指针进行了很多测试。 由于malloc在失败时返回void *,尤其是(void *)0,因此为了测试malloc成功,NULL是一个天生的事情。 由于这是非常必要的,其他库函数也使用NULL(或(void *)0),就像fopen一样。 实际上,返回指针的一切。

因此,没有理由在语言级别定义它 – 它只是一个特殊的指针值,可以由许多函数返回。