在C语言中,NULL是否总是等于零?

74

我昨天面试了一位中级软件工程师,他提到在C语言中,NULL并不总是零,并且他曾经看到过C语言的实现中NULL不是零。我觉得这很可疑,但我希望确认一下。有人知道他是否正确吗?

(回复不会影响我的候选人评估,我已经提交了决策给我的经理。)


2
http://c-faq.com/null/index.html - Jouni K. Seppänen
2
不是,但零始终是“NULL”。 - Philip
@Philip:只是字面上的零,不一定是其他零。 - Dietrich Epp
6
@Philip: int x = 0; void *p = (void *) x; 这里,x的值为零,但x并不是字面值零,因此p不能保证是NULL,在一些奇怪的平台上实际上也不会是NULL。另一方面,void *q = 0; 总是将NULL赋给q,无论在什么平台上。 在这个上下文中,“字面值”有一个技术含义。搜索“整数字面量”。 - Dietrich Epp
为了得到比下面发布的回答更完整的答案,请查看null指针和NULL之间有什么区别? 实际上回答这个问题,必须将null指针、null指针常量和NULL宏这些术语分开讲解。 - Lundin
显示剩余3条评论
5个回答

75

我假设你是指空指针。它保证与0相等比较。1 但不一定用所有零位表示。2

另请参见有关空指针的comp.lang.c FAQ


  1. 参见C99,6.3.2.3。
  2. 没有明确的声明; 但请参阅C99,7.20.3的脚注(感谢评论中的@birryree)。

8
作为你C99参考资料的补充,由于NULL始终等于0(空指针常量),第7.20.3.2节还指出,所有位都设置为零(例如在calloc中的情况)不一定与表示为0.0f或整数0的表示相同。 - wkl
很棒。这是现在非常普遍的做法,但不是必需的。谢谢。 - chi42
1
那么如果我将一个空指针转换为整数,然后打印它,我不会打印出'0'吗?这是否意味着编译器必须记住当它进行指针比较时,以便可以使空指针比较等于零? - chi42
6
标准并没有说明将指针(任何指针)转换为整型时会发生什么,除了这是实现定义的事情。请再次参考6.3.2.3节。 - Oliver Charlesworth
NULL怎么可能等于0呢?0的二进制表示全是0(对吧?),但NULL还是等于0? - einpoklum

17

C99标准的§ 6.3.2.3规定

值为0的整数常量表达式,或将此类表达式强制转换为void *类型,被称为空指针常量。如果将空指针常量转换为指针类型,则生成的指针称为空指针,并保证与任何对象或函数的指针比较时均不相等。

§ 7.17还规定

[...] NULL展开成一个实现定义的空指针常量 [...]

空指针的地址可能与0不同,在大多数情况下它会像0一样工作。

(这应该与早期的C标准相同,我现在手头没有)


16

空指针常量始终为0。实现可以将NULL宏定义为裸的0,或者像(void *) 0这样的转换表达式,或者一些其他零值整数表达式(因此标准中使用了"实现定义"语言)。

空指针值可能不是0。当遇到空指针常量时,它将被转换为适当的空指针值。


3
颜色让我显得很蠢,但我现在害怕需要更多的教育。 当你有一个指针 ptrptr==NULL难道不就等同于ptr==0!ptr吗? 这个实现是否依赖于特定系统? - Mr Lister
5
在你的源代码环境中,你是对的 - ptr == 0ptr == NULL!ptr都是等价的。然而,一旦源代码被转化为机器码,实际的空指针值可能并不是0(所有这些比较将与实际的空指针值进行比较)。 - John Bode
1
空指针值可能不是0,您是否意味着*ptr == something即使void * ptr = 0 - Incerteza
7
@AlexanderSupertramp: 我的意思是,程序执行时的运行环境可能会使用一个值来表示“空”指针(即,一个明确定义的无效地址值),而不是0。在某些系统上,0可能是您的程序可以访问的完全有效的内存地址,并且可能会使用不同的值(例如,0xFFFFFFFF)来表示“null”。但是,在您的源代码中,始终使用值为0的表达式来表示NULL指针常量。 - John Bode
只是一个新手问题:我想知道当我说“null指针的值等于存储在内存位置中的值,这个内存位置是任意选择(猜测)或由编译器在运行时选择的,当我们写char *p = NULL;时,p指向这个内存位置。”时是否正确。请指正我。 - ajaysinghnegi
显示剩余2条评论

11
在C语言中,只有在将空指针通过无类型函数参数列表传递时,才需要明确将空指针常量强制转换为特定的指针类型,以使程序正常运行。在现代C语言中,这种情况只会发生在需要将空指针传递给接受可变数量参数的函数时。(在传统的C语言中,任何未声明原型的函数都会发生这种情况。) 典型的例子是 execl,其中最后一个参数必须是显式转换为(char *)的空指针:
execl("/bin/ls", "ls", "-l", (char *)0);    // correct
execl("/bin/ls", "ls", "-l", (char *)NULL); // correct, but unnecessarily verbose

execl("/bin/ls", "ls", "-l", 0);            // undefined behavior
execl("/bin/ls", "ls", "-l", NULL);         // ALSO undefined behavior

是的,即使将NULL定义为((void *)0),最后一个示例也具有未定义行为,因为当通过未类型化的参数列表传递时,void *char *不会被隐式地互相转换,即使在其他情况下它们可以互换。 (在C2011中有关于它们在通过va_arg传递时可以隐式互换的语言,但他们忘记了指定由实现提供的库函数访问可变参数的方式类似于调用va_arg,因此您只能对包含在程序中的可变参数函数依赖这一点。有人应该提交一个DR。)

“在底层”,这里的问题不仅与用于空指针的位模式有关,而且编译器可能需要知道每个参数的确切具体类型以正确设置调用帧。 (考虑MC68000,其具有单独的地址和数据寄存器;某些ABI指定指针参数应在地址寄存器中传递,但整数参数应在数据寄存器中传递。还要考虑任何intvoid * 不具有相同大小的ABI。[编辑:我不确定,但这可能不再被允许。]如果有函数原型,编译器可以使用它,但未经原型化的函数和可变参数不提供这样的帮助。

C ++更加复杂,我不觉得自己有资格解释如何处理。


2
在某些实现中,指针的大小与整数的大小不同。在整数上下文中,NULL为0,但实际的二进制布局不一定全部为0。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接