可以假设NULL常量就是零吗?

7
《理解和使用C指针》一书中,Richard Reese提到:
“null”是一个抽象的概念,由空指针常量所支持。这个常量可以是零或非零值。C程序员不需要关心它们的内部表示。
我的问题是,既然“这个常量可以是零或非零值”,那么在我的代码中做以下操作是否安全?
int *ptr = NULL;
// Some code which probably sets ptr to a valid memory address

if(!ptr)
{
   ERROR();
}

如果NULL不等于0,if语句有可能会被评估为true。

2
!ptr 的结果与硬件中是否将空指针表示为0无关。 ! 并没有被定义为这样。 - user2357112
(此外,NULL 宏展开成什么并不重要 - 即使 NULL 展开成 0,空指针的底层表示也不需要是 0。) - user2357112
不要混淆所有不同的术语:空指针、空指针常量、NULL宏。 - Lundin
3个回答

6
可以安全地假设NULL常量为零。
NULL通常是一个零位模式。它可能是一个非零位模式,但现在很少看到。
OP混淆了至少4件事: NULL空指针常量空指针,将空指针与0进行比较。 C不定义 NULL常量
NULL 是一个扩展为实现定义的空指针常量的宏。C17dr §7.19 3
带有值0的整数常量表达式或将此类表达式强制转换为类型 void *的表达式称为空指针常量。 C17dr §§6.3.2.3 3
因此,空指针常量类型可以是 int unsigned long ,...或 void *
当整数常量表达式1具有值0时,空指针常量为0。作为指针,如((void*)0),其值/编码未指定。它普遍具有零位模式,但未指定。
可能有许多空指针常量。它们都相互比较相等。
注意:当它是整数且为符号时,空指针常量大小可能与对象指针的大小不同。通常通过需要附加一个或两个后缀的L来避免此大小差异。
如果将空指针常量转换为指针类型,则生成的指针(称为空指针)保证与任何对象或函数的指针进行比较时不相等。 C17dr §§6.3.2.3 3
将空指针转换为另一种指针类型会产生该类型的空指针。任何两个空指针应该相等。 C17dr §§6.3.2.3 4
空指针的类型是某些指针,可以是像 int *,char *这样的对象指针,也可以是像 int (*)(int, int) void *这样的函数指针。

空指针未指定。虽然它通常具有零的位模式,但其值是未指定的。

所有空指针无论编码如何,都会比较为相等。

空指针与0进行比较

if(!ptr)相当于if(!(ptr != 0))。当指针ptr(一个空指针)被与0进行比较时,0将被转换为一个指针,该指针是相同类型的空指针int *。这两个空指针可能具有不同的位模式,但它们会被比较为相等。


那么在什么情况下不能安全地假设NULL常量为零?

NULL可能是((void*)0),并且其位模式可能与零不同。无论其编码方式如何,它与上面一样比较为0。请注意,这里讨论的是指针比较,而不是整数比较。将NULL转换为整数可能不会得到一个整数值0,即使((void*)0)的所有位都是零。

printf("%ju\n", (uintmax_t)(uintptr_t)NULL); // Possible not 0

注意,这里是将指针转换为整数,而不是像if(!ptr)那样将0转换为指针。
C规范包含许多老的做法,也开放了新的方法。我从未遇到过任何一个实现中NULL不是所有零位模式的情况。鉴于许多代码假定NULL是所有零位,我怀疑只有旧的晦涩实现使用了非零位模式的NULL,并且NULL几乎肯定是所有零位模式。
1空指针常量是1)整数或2)void*类型的。在"When an integer ..."中,指的是第一种情况,而不是像 (int)((void*)0) 中第二种情况的强制转换或转换。

@EricPostpischil 关于这个,我不太确定,“值为0的整数常量表达式被称为空指针常量”-->稍微重新措辞了我的回答,以处理空指针常量是整数的情况。在我看来,当整数的值为零时,这一点非常清楚。 - chux - Reinstate Monica
@EricPostpischil 关于“(int) NULL不一定是零”的回答,我同意,因为答案中提到“NULL可能是((void*)0),其位模式可能与零不同”。 - chux - Reinstate Monica
当一个整数作为 空指针常量值 时,它的值为0。但这仍然存在问题。标准没有提供任何理由去假设当一个值为0的整数常量在源代码上下文中被用作需要解释为空指针常量时,它将被转换为一个具有所有比特位为零的指针值。我不确定您是否试图断言这一点,但这也是该语句存在问题的原因之一。 - John Bollinger
@JohnBollinger 对于“没有理由认为……整数常量值为0……需要将其解释为空指针常量”,我表示赞同。我的观点则相反,当“空指针常量”是某个整数时,它的值为0。一个不是整数的零才是“空指针常量”。 - chux - Reinstate Monica
@EricPostpischil 注意:为了解决关于解释的第一部分,已在回答中添加了注释。implementation might use 0xffffffff 已经在回答的“因此,当不能安全地假定...”部分得到了涵盖。 - chux - Reinstate Monica

4

if(!ptr)是检查空指针的安全方式。

表达式!x等同于0 == x。常量0是一个空指针常量,任何指针都可以与空指针常量比较相等。

即使空指针的表示不是“所有位都为0”,这仍然成立。

关于!运算符,C标准第6.5.3.3p5节规定:

逻辑否定运算符!的结果如果操作数的值不等于0,则为0;如果操作数的值等于0,则为1。结果的类型为int。表达式!E等同于(0==E)

关于指针转换的C标准第6.3.2.3p3节规定:

一个值为0的整型常量表达式,或者被强制类型转换为 void * 类型的表达式,被称为空指针常量。如果将空指针常量转换为指针类型,则 resulting pointer,也就是所谓的空指针,保证与任何对象或函数的指针不相等。

2
chux写了一个好的、详细的答案,但是关于那本书,我对它的质量持怀疑态度:
这是错误的,它必须始终为零或将零转换为void*。空指针常量的定义在C17 6.3.2.3/3中找到:
整数常量表达式的值为0,或者将这样的表达式强制转换为类型void*,被称为空指针常量。如果将空指针常量转换为指针类型,则得到的指针称为空指针,保证与任何对象或函数的指针比较不相等。
这意味着所有像00L0u0x0'\0'等整数常量表达式都是空指针常量。如果将它们中的任何一个强制转换为void*,它也是一个空指针常量。
作者显然混淆了两个正式术语空指针常量空指针。程序员不需要关心空指针的内部表示。他们确实需要知道什么是有效的空指针常量。最安全、最可读的方法是使用NULL宏,它保证是一个空指针常量。
因此,关于你的问题"我在我的代码中像下面这样做是安全的吗"——是的,在检查空指针时使用!ptr是完全安全的,即使ptr==NULL是更可读的代码。

使用“常量可能是零或不是零”来断言错误的微妙弱点在于,_零_是一个数字,而(void*)0是一个指针——因为指针可以具有与0不同的大小和编码。作为整数,零必须是所有0位(暂时忽略古老的非2的补码及其-0)。指针(void*)0可能具有非零的位模式。无论编码/大小如何,_空指针常量_作为整数或指针都等于0 - chux - Reinstate Monica
@chux-ReinstateMonica (void*)0虽然不是常量,但0是常量。 - Lundin
同意 0 是一个“常量”,一个“整数常量”。我得说 (void*)0 最多是一个“常量地址”。总之,“空指针常量”可以是 (void*)0 - chux - Reinstate Monica

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