简短的回答:
在 C 和 C++ 中,(int *)0
是一个常量表达式,其值为空指针。但是,它不是空指针常量。据我所知,其值为空指针的常量表达式和空指针常量之间唯一可观察到的区别是空指针常量可以分配给任何指针类型,但其值是空指针的常量表达式具有特定的指针类型,并且只能分配给具有兼容类型的左值。在 C 中,但不是 C++,(void *)0
也是一个空指针常量;void *
这是与通用 C-but-not-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++
(void *)0
在这种情况下,即使在 C 语言中,空指针常量和其值是空指针的常量表达式之间的差异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 节。这是我的总结:
常量表达式是编译器可以在不知道任何对象的值的情况下计算为常量的任何 C 表达式(或const
其他;但是,enum
值是公平的游戏),并且没有副作用。(这是对大约 25 页标准语的大幅简化,可能并不准确。)
整数常量表达式是常量表达式的受限子集,方便地在单个段落 C99 6.6p6 及其脚注中定义:
整数常量表达式96应具有整数类型,并且应仅具有整数常量、枚举常量、字符常量、sizeof
结果为整数常量的表达式和作为强制转换的直接操作数的浮点常量的操作数。整数常量表达式中的强制转换运算符只能将算术类型转换为整数类型,但作为运算sizeof
符操作数的一部分除外。
96整数常量表达式用于指定结构的位域成员的大小、枚举常量的值、数组的大小或 case 常量的值。适用于 [ #if
] 中使用的整数常量表达式的进一步约束在 6.10.1 中讨论。
出于本次讨论的目的,重要的一点是
强制转换运算符 ... 只能将算术类型转换为整数类型
这意味着它(int *)0
不是一个整数常量表达式,尽管它是一个常量表达式。
C++98 的定义似乎或多或少是等价的,模 C++ 特性和与 C 的偏差。例如,C++ 中字符和布尔类型与整数类型的更强分离意味着 C++ 标准谈到“整数常量表达式”而不是“整数常量表达式”,然后有时不仅需要一个整数常量表达式,还需要一个整数类型的整数常量表达式,不包括char
, wchar_t
, and bool
(也许还有signed char
and unsigned char
? 从文本中我不清楚)。
现在,空指针常量的 C99 定义就是这个问题的全部内容,所以我会重复一遍:6.3.2.3p3 说
值为 0 的整数常量表达式,或转换为 type 的此类表达式
void *
称为空指针常量。如果将空指针常量转换为指针类型,则生成的指针(称为空指针)保证不等于指向任何对象或函数的指针。
标准语非常非常字面。这两句话的意思完全一样:
值为 0 的整数常量表达式称为空指针常量。
一个值为 0 的整数常量表达式,强制转换为 type void *
,也是一个空指针常量。
当任何空指针常量转换为指针类型时,生成的指针称为空指针,并保证比较不等...
(斜体 - 术语的定义。粗体 - 我的重点。)所以这意味着,在 C 中,(long *)0
并且(long *)(void *)0
是两种编写完全相同的东西的方式,即带有 type 的空指针long *
。
C++ 是不同的。等效文本是 C++98 4.10 [conv.ptr]:
空指针常量是整数类型的整数常量表达式 (5.19) 右值,其计算结果为零。
就这样。“整数类型的整型常量表达式右值”与 C99 的“整型常量表达式”几乎相同,但有一些东西在 C 中符合条件但在 C++ 中不符合条件:例如,在 C 中,字符文字'\x00'
是一个整型常量表达式,因此是空指针常量,但在 C++ 中它不是整数类型的整型常量表达式,因此它也不是空指针常量。
更重要的是,C++ 没有“或这样的表达式转换为void *
”子句。这意味着这((void *)0)
不是C++中的空指针常量。它仍然是一个空指针,但它与任何其他指针类型的赋值都不兼容。这与 C++ 通常比较挑剔的类型系统是一致的。
C++11(但不是,AFAIK,C11)修改了“空指针”的概念,为它们添加了一个特殊类型(nullptr_t
)和一个新的关键字,它计算为一个空指针常量(nullptr
)。我不完全理解这些变化,也不会试图解释它们,但我很确定0
在 C++11 中裸露仍然是一个有效的空指针常量。