C语言 每个空指针都是空指针吗?

kadbb459  于 2023-05-16  发布在  其他
关注(0)|答案(6)|浏览(232)

根据C17草案(6.3.2.3 ¶3):
一个值为0的整数常量表达式,或者一个转换为void *类型的表达式,被称为 *null指针常量 *。67)如果一个null指针常量被转换为指针类型,那么得到的指针,被称为 *null指针 *,保证与指向任何对象或函数的指针相比不相等。
67)宏NULL<stddef.h>(和其他头文件)中定义为空指针常量[...]。
由此可知,以下是空指针常量00UL(void *)0(void *)0ULNULL
下面是空指针(int *)0(int *)0UL(int *)(void *)0(int *)(void *)0UL(int *)NULL。有趣的是,这些都不是“空指针常量”;参见here
以下空指针常量 * 是 * 空指针(因为void *是指针类型,而00UL是空指针常量):(void *)0(void *)0UL。在这方面,根据C17草案(6.2.5 ¶19-20):
void类型包括空的值集合;它是一个不能完成的不完整对象类型。
[...]

  • 指针类型 * 可以从函数类型或对象类型派生,称为 * 引用类型 *。[...]指针类型是一个完整的对象类型。
    void本身不是指针类型,它是一个不完整的对象类型。但是void *是一个指针类型。
    但似乎以下是不是空指针的空指针常量(因为没有转换为指针类型):00ULNULL。(准确地说,虽然标准只要求NULL定义为“空指针常量”,但允许将其定义为空指针常量,也是空指针。但似乎标准并不要求NULL以这样的方式定义,即它同时是一个空指针。

每个空指针都是空指针吗?NULL * 真的 * 不是空指针吗?)

最后(有点半开玩笑):如果某些空指针常量不是空指针,它们在技术上会是一种“非空指针”吗?(这一措辞出现在标准的某些地方。)请注意,在语言学上我们有一个所谓的 * 括号悖论 *;我们可以将其读作“[non-null] pointer”或“non-[null pointer]"。

vuktfyat

vuktfyat1#

每个空指针都是空指针吗?

**TL;DR:**否。

正如您已经观察到的,值为0的整数常量表达式是 *null指针常量 *,尽管没有指针类型。您还引用了规范中对 null pointer 的定义:“将空指针常量[]转换为指针类型”。这意味着这种一般形式的空指针常量。。

(void *)(<integer constant expression with value 0>)

...满足“空指针”的定义。整数常量表达式本身就是一个空指针常量,因此强制转换使整个表达式成为一个空指针(除了是一个空指针常量之外)。
另一方面,采用值为0的整数常量表达式形式的空指针常量不满足“空指针”的定义,并且在语言规范中没有其他规定使它们成为空指针。例如:00x00UL1 + 2 + 3 - 6
看起来标准并不要求NULL以这样一种方式定义,即它同时是一个空指针。
是的。

每个空指针都是空指针吗?

当然不是(见上文),但对于大多数目的来说,这并不重要。
(Is NULL真的不是空指针吗?)
这取决于你的C实现。语言规范允许任何一种答案。实际上,在你可能遇到的大多数实现中,它都是一个空指针。
如果某些空指针常量不是空指针,它们在技术上会是一种“非空指针”吗?
不是。非空指针的空指针常量根本不是指针。它们是整数。

voj3qocg

voj3qocg2#

每个空指针都是空指针吗?

不,原因在你引用的文本中:

如果一个空指针常量被转换为指针类型,那么得到的指针,称为空指针,保证与指向任何对象或函数的指针相比不相等。

一个空指针 constant 不会自动成为指针,就像任何整数常量不会自动成为指针一样。必须将常数值转换为指针类型以生成空指针。
结果空指针 * 不一定是零值 *。它只需要是一个不能是任何对象或函数的地址的值。这个值 * 可能是0x00000000(在我熟悉的实现中是这样的),也可能是0xFFFFFFFF,也可能是0xDEADBEEF,也可能是其他值。

db2dz4w8

db2dz4w83#

  • null指针常量 * 可以是void *或某种 integer 类型。

在您的机器上测试:

#include <stdio.h>
#include <stdlib.h>

#define NULL_TEST(n) _Generic((n), \
  void *: "void *", \
  int: "int", \
  long: "long", \
  default: "something else" \
)

int main(void) {
  printf("%s\n", NULL_TEST(NULL));
  printf("%s\n", NULL_TEST((void*)0));
  printf("%s\n", NULL_TEST(0));
  printf("%s\n", NULL_TEST(0L));
}

在我的机器上,我有下面的输出。第一行的输出可能会有所不同。

void *
void *
int
long
qyyhg6bp

qyyhg6bp4#

不。事实上,没有空指针常量是空指针!这是因为常量和指针是不同种类的实体。
空指针常量是一个具有特定形式的常量***表达式***。表达式是一个标记序列,空指针常量定义为具有特定形式的标记序列。
空指针是一个***值***。在C中,每个类型都有一组潜在的值。对于每个指针类型,该集合中的一个或多个值是空指针。C标准没有正式定义值的概念。一个正式的语义需要这样做(正式定义指针的值变得相当复杂,这就是为什么C标准,一个没有数学的英语文档,没有尝试)。
表达式*将*计算为上下文中的值(可能导致副作用)。类型为指针类型的所有空指针常量的计算结果均为空指针。一些空指针常量(例如01L - 'z' / 'z')具有整数类型,并且它们的计算结果不为空指针:它们的值为空整数(即,一个值为0的整数-C标准不使用表达式“null integer”,因为它不是需要特定名称的任何显着内容)。
C标准保证,如果 e 是一个
常量
表达式,具有整数类型和值0,那么任何将此值转换为指针类型的表达式都将计算为空指针。请注意,此保证不适用于任意表达式:即使f被定义为int f(void) { return 0; }(void*) f()也可能不是空指针。
C标准允许NULL具有整数类型或指针类型。如果它有指针类型,表达式NULL计算为空指针。如果它有一个整数类型,它就没有。

wztqucjr

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*可能被隐式转换为非指针的表达式(如布尔值),则编译器可以捕获错误。

azpvetkf

azpvetkf6#

C的设计方式是,至少在它最初的目标平台上,指针和整数在大多数上下文中基本上可以互换。给定char *p; int i;,处理p=0;的编译器将以与i=0;基本相同的方式处理它,除了前者将值0写入p的地址,而后者将值0存储到i的地址。编译器不需要理解空指针的概念,因为用于将i设置为数值零的相同编译器逻辑可以有效地将p设置为一个与任何对象都不相关的值,并且表现为值零。
C标准的编写方式不允许表达式的类型根据使用它的上下文而变化。虽然说p=0;中赋值运算符的右操作数将具有指针类型,而i=0;中的右操作符将具有整数类型可能是有意义的,但标准的设计要求它们具有相同的类型。因为没有一个“正常”类型可以同时在两种上下文中使用,所以C标准的作者为表达式创建了一个特殊的“类型”,它应该在两种上下文中都可以使用。我认为术语“空指针”常量比必要的更令人困惑,而“通用零”会更清楚,因为零代表的不仅仅是数字零,或空指针,或全零位模式,而是更一般的静态持续时间对象的默认值。

相关问题