根据C17草案(6.3.2.3 ¶3):
一个值为0的整数常量表达式,或者一个转换为void *
类型的表达式,被称为 *null指针常量 *。67)如果一个null指针常量被转换为指针类型,那么得到的指针,被称为 *null指针 *,保证与指向任何对象或函数的指针相比不相等。
67)宏NULL
在<stddef.h>
(和其他头文件)中定义为空指针常量[...]。
由此可知,以下是空指针常量:0
、0UL
、(void *)0
、(void *)0UL
、NULL
。
下面是空指针:(int *)0
、(int *)0UL
、(int *)(void *)0
、(int *)(void *)0UL
、(int *)NULL
。有趣的是,这些都不是“空指针常量”;参见here。
以下空指针常量 * 是 * 空指针(因为void *
是指针类型,而0
和0UL
是空指针常量):(void *)0
、(void *)0UL
。在这方面,根据C17草案(6.2.5 ¶19-20):void
类型包括空的值集合;它是一个不能完成的不完整对象类型。
[...]
- 指针类型 * 可以从函数类型或对象类型派生,称为 * 引用类型 *。[...]指针类型是一个完整的对象类型。
void
本身不是指针类型,它是一个不完整的对象类型。但是void *
是一个指针类型。
但似乎以下是不是空指针的空指针常量(因为没有转换为指针类型):0
、0UL
、NULL
。(准确地说,虽然标准只要求NULL
定义为“空指针常量”,但允许将其定义为空指针常量,也是空指针。但似乎标准并不要求NULL
以这样的方式定义,即它同时是一个空指针。
每个空指针都是空指针吗?(NULL
* 真的 * 不是空指针吗?)
最后(有点半开玩笑):如果某些空指针常量不是空指针,它们在技术上会是一种“非空指针”吗?(这一措辞出现在标准的某些地方。)请注意,在语言学上我们有一个所谓的 * 括号悖论 *;我们可以将其读作“[non-null] pointer”或“non-[null pointer]"。
6条答案
按热度按时间vuktfyat1#
每个空指针都是空指针吗?
**TL;DR:**否。
正如您已经观察到的,值为0的整数常量表达式是 *null指针常量 *,尽管没有指针类型。您还引用了规范中对 null pointer 的定义:“将空指针常量[]转换为指针类型”。这意味着这种一般形式的空指针常量。。
...满足“空指针”的定义。整数常量表达式本身就是一个空指针常量,因此强制转换使整个表达式成为一个空指针(除了是一个空指针常量之外)。
另一方面,采用值为0的整数常量表达式形式的空指针常量不满足“空指针”的定义,并且在语言规范中没有其他规定使它们成为空指针。例如:
0
、0x00UL
、1 + 2 + 3 - 6
。看起来标准并不要求NULL以这样一种方式定义,即它同时是一个空指针。
是的。
每个空指针都是空指针吗?
当然不是(见上文),但对于大多数目的来说,这并不重要。
(Is
NULL
真的不是空指针吗?)这取决于你的C实现。语言规范允许任何一种答案。实际上,在你可能遇到的大多数实现中,它都是一个空指针。
如果某些空指针常量不是空指针,它们在技术上会是一种“非空指针”吗?
不是。非空指针的空指针常量根本不是指针。它们是整数。
voj3qocg2#
每个空指针都是空指针吗?
不,原因在你引用的文本中:
如果一个空指针常量被转换为指针类型,那么得到的指针,称为空指针,保证与指向任何对象或函数的指针相比不相等。
一个空指针 constant 不会自动成为指针,就像任何整数常量不会自动成为指针一样。必须将常数值转换为指针类型以生成空指针。
结果空指针 * 不一定是零值 *。它只需要是一个不能是任何对象或函数的地址的值。这个值 * 可能是
0x00000000
(在我熟悉的实现中是这样的),也可能是0xFFFFFFFF
,也可能是0xDEADBEEF
,也可能是其他值。db2dz4w83#
void *
或某种 integer 类型。在您的机器上测试:
在我的机器上,我有下面的输出。第一行的输出可能会有所不同。
qyyhg6bp4#
不。事实上,没有空指针常量是空指针!这是因为常量和指针是不同种类的实体。
空指针常量是一个具有特定形式的常量***表达式***。表达式是一个标记序列,空指针常量定义为具有特定形式的标记序列。
空指针是一个***值***。在C中,每个类型都有一组潜在的值。对于每个指针类型,该集合中的一个或多个值是空指针。C标准没有正式定义值的概念。一个正式的语义需要这样做(正式定义指针的值变得相当复杂,这就是为什么C标准,一个没有数学的英语文档,没有尝试)。
表达式*将*计算为上下文中的值(可能导致副作用)。类型为指针类型的所有空指针常量的计算结果均为空指针。一些空指针常量(例如
0
,1L - 'z' / 'z'
)具有整数类型,并且它们的计算结果不为空指针:它们的值为空整数(即,一个值为0的整数-C标准不使用表达式“null integer”,因为它不是需要特定名称的任何显着内容)。C标准保证,如果 e 是一个常量表达式,具有整数类型和值0,那么任何将此值转换为指针类型的表达式都将计算为空指针。请注意,此保证不适用于任意表达式:即使
f
被定义为int f(void) { return 0; }
,(void*) f()
也可能不是空指针。C标准允许
NULL
具有整数类型或指针类型。如果它有指针类型,表达式NULL
计算为空指针。如果它有一个整数类型,它就没有。wztqucjr5#
另一个有趣的例子是
'\0'
的类型为int
(6.4.4.3(10)),并且“这样形成的八进制整数的数值指定了所需字符或宽字符的值”((5)),十六进制转义也是如此。所以'\0'
和'\x0'
都是空指针常量。此外,“作为强制转换的立即操作数的浮点操作数”(必须是算术类型到整数类型的强制转换)属于法律的的“整数常量表达式”,因此(int)0.0
是空指针常量。所以可能是enum
值,sizeof
的结果(尽管所有标准类型的大小都至少为1,但一些编译器将零大小字段作为扩展)和_Alignof
的结果(尽管标准规定这只能返回2的正幂,并且忽略0的对齐),以及操作数为整数类型的运算符的结果,例如X^X
或!1
。一些现代编译器将
NULL
定义为特殊关键字,例如gcc中的__null
,或者在C++上交叉编译时的nullptr
。如果程序使用了NULL
,其中整数常量或void*
可能被隐式转换为非指针的表达式(如布尔值),则编译器可以捕获错误。azpvetkf6#
C的设计方式是,至少在它最初的目标平台上,指针和整数在大多数上下文中基本上可以互换。给定
char *p; int i;
,处理p=0;
的编译器将以与i=0;
基本相同的方式处理它,除了前者将值0写入p
的地址,而后者将值0存储到i
的地址。编译器不需要理解空指针的概念,因为用于将i
设置为数值零的相同编译器逻辑可以有效地将p
设置为一个与任何对象都不相关的值,并且表现为值零。C标准的编写方式不允许表达式的类型根据使用它的上下文而变化。虽然说
p=0;
中赋值运算符的右操作数将具有指针类型,而i=0;
中的右操作符将具有整数类型可能是有意义的,但标准的设计要求它们具有相同的类型。因为没有一个“正常”类型可以同时在两种上下文中使用,所以C标准的作者为表达式创建了一个特殊的“类型”,它应该在两种上下文中都可以使用。我认为术语“空指针”常量比必要的更令人困惑,而“通用零”会更清楚,因为零代表的不仅仅是数字零,或空指针,或全零位模式,而是更一般的静态持续时间对象的默认值。