【问题标题】:Is ((void*)0) a null pointer constant?((void*)0) 是空指针常量吗?
【发布时间】:2014-12-16 02:49:34
【问题描述】:

我正在阅读 this blog post 并在 Null pointer constants and parenthesized expressions 部分下,作者引用了 ISO C 标准中的 § 6.3.2.3 和 § 6.5.1 并说:

没有说带括号的空指针常量是空指针常量。

这意味着,严格来说,(void*)0 是一个空指针 不变,但((void*)0) 不是。

然后:

我确信大多数 C 实现确实将带括号的空指针常量视为空指针常量,并将 NULL 定义为 0((void*)0) 或其他方式。

引用的两个部分说:

§ 6.3.2.3

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

§ 6.5.1

带括号的表达式是主表达式。 它的类型和价值 与未加括号的表达式相同。 左值、函数指示符或 void 表达式,如果 不带括号的表达式分别是一个左值、一个函数 指示符或空表达式。

加粗的句子是不是和作者声称((void*)0)不是空指针常量相矛盾?

【问题讨论】:

标签: c language-lawyer


【解决方案1】:

加粗的句子是不是和作者声称((void*)0)不是空指针常量相矛盾?

不,它没有。 (我承认有点偏见,因为引用的博客是我的。)

加粗的句子表示它的typevalue与未加括号的表达式相同。这还不足以暗示它是一个空指针常量。

考虑:

void *var = 0;

(void*)0 是一个空指针常量。 ((void*)0)(void*)0 具有相同的类型和值。 var (void*)0具有相同的类型和值,但var显然不是空指针常量。

话虽如此,我 99+% 确信 意图((void*)0) 是一个空指针常量,更一般地说,任何带括号的空指针常量都是一个空指针常量。该标准的作者只是忽略了这样说。并且由于 6.5.1p5 中对括号表达式的描述特别列举了括号表达式继承的其他几个特征:

带括号的表达式是主表达式。它的类型和价值 与未加括号的表达式相同。它是一个 左值、函数指示符或 void 表达式,如果 不带括号的表达式分别是一个左值、一个函数 指示符或空表达式。

这个遗漏令人不安(但只是轻微的)。

但是,为了论证,我们假设((void*)0) 不是空指针常量。有什么区别?

(void*)0是一个空指针常量,它的值是void*类型的空指针,所以根据括号表达式的语义((void*)0)也有一个值是void*类型的空指针。 (void*)0((void*)0) 都是地址常量。 (嗯,我认为它们是。)那么什么上下文需要空指针常量而不接受地址常量?只有几个。

6.5.9 等式运算符

函数指针类型的表达式可以与空指针常量比较是否相等。 (对象指针可以与void* 类型的表达式进行比较,但函数指针可能不会,除非它是空指针常量。)所以:

void func(void);
if (func == ((void*)0)) { /* ... */ }

会违反约束。

6.5.16.1 简单赋值

在赋值中,一个空指针常量可以被赋值给一个指向函数类型的对象,并且会被隐式转换。不是空指针常量的void* 类型的表达式不能分配给函数指针。相同的约束适用于参数传递和初始化。所以这个:

void (*fp)(void) = ((void*)0);

如果((void*)0) 不是空指针常量,则将违反约束。感谢评论者 hvd 找到这个。

7.19 常用定义<stddef.h>

NULL 扩展为“实现定义的空指针常量”。如果((void*)0) 不是空指针常量,那么:

#define NULL ((void*)0)

无效。这将是对实现的限制,而不是对程序员的限制。请注意:

#define NULL (void*)0

绝对无效,因为标准头文件中的宏定义必须在必要时用括号完全保护(7.1.2p5)。如果没有括号,则有效表达式 sizeof NULL 将是一个语法错误,扩展为 sizeof (void*) 后跟一个无关常量 0

【讨论】:

  • 一个缺点:让你的论点贯穿void* var 的缺点是var 不是一个空指针常量,因为它也恰好不是一个ICE
  • 您能否详细说明“非空指针常量”是什么意思?我的意思是,var 不是一,因为它不是一个常数,但 ((void*)0) 不是一意味着什么?由于这种疏忽,规范是否允许在技术上出现任何不正当的编译器行为?
  • @KeithThompson 6.3.2.3 第 4 条:将空指针转换为另一种指针类型会产生该类型的空指针。任何两个空指针应该比较相等。
  • 只是在想,上一句说“如果将空指针常量转换为指针类型,则生成的指针[is]称为空指针”。严格的阅读可能表明 weird 根本不需要是空指针,如果这种转换没有发生,因为 ((void*)0) 首先不是空指针常量。如果weird 不是空指针,那么它不需要比较等于一。当然,如果将括号添加到 (void*)0 本身构成转换为指针类型(如果 ((void*)0) 是指针类型,则很可能会发生这种情况),这一切都是无稽之谈。
  • 仅仅将((void*)0) 转换为指向函数的类型就足以证明问题所在,即使没有任何比较:void (*fp)(void) = ((void*)0); 当且仅当((void*)0) 是一个NPC 时才有效。或许同样有趣的是,C++ 已经用一个更简单的语义替换了括号的语义:“带括号的表达式可以在与可以使用封闭表达式的上下文完全相同的上下文中使用,并且具有相同的含义,除非另有说明。”例如,这也会影响char str[] = ("abc");,它在 C++ 中有效,但在 C 中无效。
【解决方案2】:

它是一个带括号的表达式,其中包含一个空指针常量,因此它无疑是一个空指针值。将其用作右值与将“兼容”版本用作右值的效果完全相同。

如果有一些句法规则可以接受一个空指针常量,它就不符合条件。但我什么都不知道(尽管我对 C 语言不太熟悉)。

虽然两者都不是常量(指形式语法产生式),但两者都可以出现在初始化器中的常量表达式中,因为空指针常量和地址常量都是允许的,并且常量空指针值明确包含在地址常量类中。

指针比较还特别提到了空指针常量……但这里也接受指针值,并且所有空指针值都被平等对待。三元运算符和赋值运算符也是如此。

请注意,这些规则在 C++ 中完全不同,其中上述两个表达式都是 void* 类型的常量空指针值,而不是通用空指针常量。 C++ 中的空指针常量是整数常量表达式,其计算结果为零。并且void* 不会隐式转换为其他指针类型。

【讨论】:

  • “[(void*)0((void*)0)] 都不是常数”?该标准明确声称(void*)0 是一个空指针常量。
  • @IwillnotexistIdonotexist: constant 的语法产生式只包括 integer-constant, floating-constant, 枚举常量字符常量。这些指针值是常量表达式,但不是常量。您认为“常量”一词包含各种整数常量、地址常量、空指针常量等的概念在这里不成立;英语语言的通常规则不适用,形式语法适用。
  • 也许您可以将 constant 的使用区分为“虽然两者都不是 syntactical 常量,但两者都可以...”?
  • 事实证明,至少有一个上下文允许空指针常量,但((void*)0),如果不是 NPC 则不会。函数指针类型的表达式可以与空指针常量比较相等或不相等,但不能与void* 类型的任何其他常量表达式进行比较。有关详细信息,请参阅我的更新答案。
【解决方案3】:

尝试在您的 C 代码中打印以下行:

printf("%p",(void*)0);

你会得到如下输出:

(无)

【讨论】:

  • (char*)0(void*){0} 也可以使用,但 (char*)0(void*){0} 不是空指针常量,而 (void*)0 是。
猜你喜欢
  • 2014-02-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-02-02
  • 2014-05-30
  • 2022-11-10
  • 2022-01-01
相关资源
最近更新 更多