(int *)0是一个空指针吗?

22

这可以被视为对这个问题的扩展(我只关心C语言,但为了完整性加上了C++)。

C11标准在6.3.2.3.3处规定:

值为0的整型常量表达式,或者将这样一个表达式强制类型转换为 void * 类型,称为空指针常量。

我的个人理解是,0(void *)0 表示空指针,它们的整数值可能实际上不是0,但这并不包括将0强制转换为任何其他类型。

但是,标准接着说:

如果将空指针常量转换为指针类型,则生成的指针称为空指针,...

这意味着 (int *)0 被认为是空指针,因为强制类型转换是一种显式转换(C11, 6.3),列在转换方法下。

然而,仍然让我感到困惑的是以下短语:

或者将这样一个表达式强制类型转换为 void * 类型 ...

按照以上语义,这个短语似乎完全没有用处。问题是,这个短语是否完全没有用处?如果不是,它有什么含义?因此,(int *)0 是空指针还是不是?


另一个有助于讨论的问题是以下内容。将(long long)123认为是“将123转换为long long”,还是“将123视为long long类型”。换句话说,在(long long)123中是否有任何转换?如果没有,则上面的第二句话不包括(int *)0作为空指针。


可能需要查看https://dev59.com/FnE85IYBdhLWcg3w03Dz。 - Colonel Thirty Two
3
(int*)0 是一个空指针,而不是唯一的。 - David Rodríguez - dribeas
@DavidRodríguez-dribeas,你是对的,但由于“任何两个空指针都应该相等”(C11,6.3.2.3-4),区分它们实际上并没有太大的区别。 - Shahbaz
你可能想看一下我在这里的问题:https://dev59.com/nm865IYBdhLWcg3wSMkw - Vicky
@Shahbaz:这完全取决于情况。int*空指针和double*空指针是否相同?我想在 C 中是这种情况... - David Rodríguez - dribeas
2个回答

35

简短回答:

在C和C++中,(int *)0 是一个值为 null 的常量表达式,它是一个空指针。然而,它不是一个 null 指针常量。据我所知,一个值为 null 的常量表达式和一个空指针常量之间唯一可观察的区别是,空指针常量可以赋值给任何指针类型的左值,但一个值为 null 的常量表达式具有特定的指针类型,只能赋值给具有兼容类型的左值。在C中,但不在C++中,(void *)0 也是一个空指针常量;这是对于 void * 的特殊情况,与一般的C但不适用于C++的规则一致,即 void * 可以与任何其他对象指针类型进行赋值兼容。

例如:

long *a = 0;           // ok, 0 is a null pointer constant
long *b = (long *)0;   // ok, (long *)0 is a null pointer with appropriate type
long *c = (void *)0;   // ok in C, invalid conversion in C++
long *d = (int *)0;    // invalid conversion in both C and C++

这里有一个案例,可以看到在C语言中,空指针常量 (void *)0 和一个值为null的具有类型 void * 的常量表达式之间的区别。
typedef void (*fp)(void);  // any pointer-to-function type will show this effect

fp a = 0;                  // ok, null pointer constant
fp b = (void *)0;          // ok in C, invalid conversion in C++
fp c = (void *)(void *)0;  // invalid conversion in both C and C++

此外,现在这个问题已经没有意义了,但既然你提到了:无论long *的空指针的位表示是什么,所有这些断言都会按照注释所示的方式运行。
// 'x' is initialized to a null pointer
long *x = 0;

// 'y' is initialized to all-bits-zero, which may or may not be the
// representation of a null pointer; moreover, it might be a "trap
// representation", UB even to access
long *y;
memset(&y, 0, sizeof y);

assert (x == 0);         // must succeed
assert (x == (long *)0); // must succeed
assert (x == (void *)0); // must succeed in C, unspecified behavior in C++
assert (x == (int *)0);  // invalid comparison in both C and C++

assert (memcmp(&x, &y, sizeof y) == 0); // unspecified

assert (y == 0);         // UNDEFINED BEHAVIOR: y may be a trap representation
assert (y == x);         // UNDEFINED BEHAVIOR: y may be a trap representation

"未指定"的比较不会引发未定义行为,但标准并未说明它们是评估为真还是假,并且实现不需要记录其中之一,甚至选择一个并坚持使用。如果您多次调用上述的memcmp,它交替返回0和1也是完全有效的。
长篇回答,带有标准引用:
要理解什么是“空指针常量”,首先你必须理解什么是“整数常量表达式”,而这个概念相当复杂——完全理解需要详细阅读C99的6.5和6.6节。以下是我的总结:
常量表达式是编译器可以在不知道任何对象的值(无论是const还是其他)的情况下计算出一个常量的任何C表达式,并且没有副作用。(这是对大约25页标准的极简化描述,可能不完全准确。)
整数常量表达式是常量表达式的一种受限子集,在C99 6.6p6及其脚注中方便地定义为一个段落:
整数常量表达式应具有整数类型,并且只能具有整数常量、枚举常量、字符常量、结果为整数常量的sizeof表达式以及作为强制转换的立即操作数的浮点常量作为操作数。整数常量表达式中的强制转换运算符只能将算术类型转换为整数类型,除非它是sizeof运算符的操作数的一部分。
整数常量表达式用于指定结构体的位字段成员的大小、枚举常量的值、数组的大小或case常量的值。用于#if中使用的整数常量表达式的进一步约束在6.10.1中讨论。
对于本讨论而言,重要的是
强制转换运算符...只能将算术类型转换为整数类型
这意味着(int *)0不是整数常量表达式,尽管它是一个常量表达式。
C++98的定义似乎在某种程度上与C++功能和偏差等价。例如,C++中字符和布尔类型与整数类型的更强分离意味着C++标准使用"整数常量表达式"而不是"整型常量表达式",有时候要求的不仅仅是整数类型的整数常量表达式,还要排除charwchar_tbool(也许还包括signed charunsigned char?从文本中我无法确定)。
现在,C99对于空指针常量的定义正是这个问题的关键,所以我会重复一下:6.3.2.3p3说:

值为0的整数常量表达式,或者将这样的表达式转换为void *类型,被称为空指针常量。如果将空指针常量转换为指针类型,得到的指针被称为空指针,保证与任何对象或函数的指针比较时都不相等。

Standardese非常、非常字面。这两个句子的意思与以下内容完全相同:
整数常量表达式值为0的被称为“空指针常量”。 整数常量表达式值为0,强制转换为类型“void *”,也是一个“空指针常量”。 当任何空指针常量转换为指针类型时,所得到的指针被称为“空指针”,并且保证不相等。
(斜体-术语定义。粗体-我的强调。)所以这意味着,在C中,“(long *)0”和“(long *)(void *)0”是写同样的东西的两种方式,即具有类型“long *”的空指针。
C++则不同。相应的文本是C++98 4.10 [conv.ptr]:
“空指针常量”是一个整数类型的积分常量表达式(5.19),其求值为零。

就这些。"整数类型的整数常量表达式右值"与C99的"整数常量表达式"几乎是一样的,但在C中有一些限定条件而在C++中没有:例如,在C中,字符字面值'\x00'是一个整数常量表达式,因此是一个空指针常量,但在C++中它不是整数类型的整数常量表达式,所以也不是空指针常量。

更重要的是,C++中没有"或将此类表达式强制转换为void *"的子句。这意味着((void *)0)在C++中不是空指针常量。它仍然是一个空指针,但它与任何其他指针类型都不兼容。这与C++的类型系统通常较为严格的特点一致。


C++ 2011和C 2023都修订了"空指针"的概念,为其添加了一个特殊类型(nullptr_t)和一个新的关键字(nullptr),该关键字可用于表示空指针常量。我并不完全理解这些变化,也不打算试图解释它们,但我相当确定裸露的0在两者中仍然是一个有效的空指针常量。

如果您能说明一下您所讨论的标准,比如C或C++,我会非常感激。此外,您能否举一个可以具有(void *)0但不具有(int *)0的常量表达式的例子? - Shahbaz
C,特别是C99;我还没有阅读过C11,并且我不记得(void *)0在C++中是否仍然是空指针常量。在这个领域,C和C++之间确实存在差异,例如,在C++中,NULL不允许扩展为((void *)0) - zwol
@Zack 无论是 C 还是 C++,都需要使用 NULL 表示空指针常量。在 C99 中,这包括 (void *)0,但是在 C++ 中,空指针常量被定义为“一个整数类型的积分常量表达式 rvalue,其值为零”。 - D Krueger
@DKrueger 我记不清C++是否将((void *)0)排除在空指针常量的定义之外,或者它只是说NULL不允许扩展为该表达式。看起来应该是前者。 - zwol
2
我不确定为什么 fp c = (void *)(void *)0 是无效的。有人能解释一下吗? - iBug
显示剩余6条评论

12
评估表达式(int*)0会生成类型为int*的空指针。 (int*)0不是空指针常量。 空指针常量是在C源代码中可能出现的特定类型的表达式。 空指针是程序运行中可能出现的值。
C和C ++(作为两种不同的语言)在这个领域有稍微不同的规则。 C ++没有“或将此类表达式转换为类型 void * ”的措辞。但我认为这并不影响你问题的答案。
至于你关于(long long)123的问题,我不确定它与此有何关联,但表达式123是 int 类型,并且强制转换指定从int到 long long 的转换。
我认为核心的困惑在于假设在(int*)0中的强制转换没有指定转换,因为0已经是< em>空指针常量。 但是,空指针常量不一定是指针类型的表达式。 特别地,表达式0既是空指针常量,也是int类型的表达式; 它不属于任何指针类型。需要将术语空指针常量视为单个概念,而不是一个短语,其含义取决于构成它的个别单词。

然而(在Windows 7下使用GCC-4.8.1 / MinGW-32),(int *)0 == NULL似乎会评估为1(即真)。因此,它确实等于空指针常量。这是标准的还是实现定义的? - Kninnug
我已经修复了NULL的情况。但是,这是否是您对[language-lawyer]标签的回答呢?还是我应该期望在开始阅读答案之前每分钟进行10次编辑? - Shahbaz
@n.m.,我对此没有异议。但(int *)0是一个空指针吗?Keith说是,但没有提供任何参考或理由。他可能只是在发表个人意见。 - Shahbaz
@Shahbaz:你自己引用了标准的相关部分。0是一个空指针常量。将其转换为任何指针类型都会产生一个空指针 - Keith Thompson
@KeithThompson,这就是第二个问题的所在。如果(long long)123是一个转换为long longint,那么是的(int *)0是一个转换为int *的空指针。但是,如果(long long)123是一个值为123的直接long long数字(没有转换),那么(int *)0也将是一个值为0的int *指针。我正在标准中搜索这个问题。 - Shahbaz
显示剩余3条评论

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