为什么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
,在其他平台上, 0L
或0ULL
或其他适当的值。 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一样。 实际上,返回指针的一切。
因此,没有理由在语言级别定义它 – 它只是一个特殊的指针值,可以由许多函数返回。