52

注意:这是一个问题,尽管我添加了以防某些 C++ 专家可以提供 C++ 使用与 C 不同的措辞的基本原理或历史原因。


在 C 标准库规范中,我们有这个规范文本,C17 7.1.3 Reserved identifiers(强调我的):

  • 所有以下划线和大写字母或另一个下划线开头的标识符始终保留用于任何用途
  • 所有以下划线开头的标识符始终保留用作普通和标记名称空间中具有文件范围的标识符。

现在,我一直在阅读各种受人尊敬的 C 专家关于 SO 的答案,他们声称编译器或标准库可以使用带有下划线 + 大写或双下划线的标识符。

“保留用于任何用途”是否意味着保留给除了 C 语言本身的未来扩展之外的任何人?这意味着不允许实现使用它们。

虽然上面的第二个短语,关于单个前导下划线似乎是针对实现的?

通常,C 标准的编写方式期望编译器供应商/库实现者是典型的读者——而不是应用程序程序员。

值得注意的是,C++ 有一个非常不同的措辞:

  • 每个包含双下划线 ( __) 或以下划线后跟大写字母 (2.11)的名称都保留给实现以供任何使用

(请参阅关于在 C++ 标识符中使用下划线的规则是什么?

这可能是 C 和 C++ 之间的混淆,并且这里的语言不同吗?

4

6 回答 6

34

在 C 标准中,术语“保留”的含义由 7.1.3p2 定义,紧邻您引用的项目符号列表下方:

没有保留其他标识符。如果程序在保留标识符的上下文中声明或定义标识符(7.1.4 允许的除外),或将保留标识符定义为宏名称,则行为未定义。

强调我的:保留的标识符限制了程序,而不是实现。因此,常见的解释——保留标识符可以被实现用于任何目的——对于 C 是正确的。

我没有跟上 C++ 标准,不再觉得有资格解释它。

于 2018-09-17T12:20:10.760 回答
16

虽然该标准主要是为指导实施者而编写的,但它是作为对什么使程序格式良好以及其效果的描述而编写的。这是因为符合标准的编译器的基本定义是为任何符合标准的程序做正确的事情:

严格遵守的程序应仅使用本国际标准中指定的语言和库的那些特性……遵守的托管实现应接受任何严格遵守的程序。

单独阅读,这极大地限制了编译器的扩展。例如,仅基于该子句,编译器不应该定义任何自己的保留字。毕竟,特定编译器可能想要保留的任何给定单词,仍然可能出现在严格符合的程序中,从而迫使编译器手动操作。

然而,标准还在继续:

一个符合要求的实现可能有扩展(包括额外的库函数),只要它们不改变任何严格符合的程序的行为。

这是关键部分。编译器扩展需要以这样一种方式编写,即它们会影响不符合标准的程序(包含未定义行为的程序,或者根本不应该编译的程序),允许它们编译并做一些有趣的额外事情。

因此,定义“保留标识符”的目的是,当语言实际上不需要这些标识符来做任何事情时,通过为实现提供一些使程序不符合标准的东西来为实现提供一些额外的回旋余地。例如,编译器可以将其识别__declspec为声明的一部分的原因是因为放入__declspec声明中是非法的,因此允许编译器做它想做的任何事情!

因此,“为任何用途保留”的重要性在于,它毫无疑问地表明编译器有能力将此类标识符视为具有它关心的任何含义。未来的兼容性是一个相对遥远的问题。

C++ 标准以类似的方式工作,尽管它对策略更加明确:

一个符合规范的实现可以有扩展(包括额外的库函数),只要它们不改变任何格式良好的程序的行为。需要实现来诊断使用根据本国际标准格式错误的扩展的程序。然而,这样做之后,他们可以编译和执行这样的程序。

我怀疑措辞上的差异归结于 C++ 标准,只是更清楚地说明了扩展的工作原理。尽管如此,C 标准中的任何内容都不会阻止实现做同样的事情。(而且我们基本上都忽略了编译器每次使用时都会警告您的要求__declspec。)

于 2018-09-17T14:07:46.633 回答
15

关于 C 与 C++ 的措辞差异,我在这里发布我自己的小研究作为参考:

  • 早期的K&R C 第 1 版有这样的文字:

    ...仅供库函数使用的名称以下划线开头,因此它们不太可能与用户程序中的名称发生冲突。

  • K&R 第 2 版添加了一个附录 B,它涉及标准库,我们可以在其中阅读

    以下划线开头的外部标识符保留给库使用,所有其他以下划线和大写字母或另一个下划线开头的标识符也是如此。

  • 早期的 ANSI C 草案以及“C90”ISO 9899:1990 的文本与当前 ISO 标准中的文本相同。

  • 然而,正如@hvd 所指出的,最早的 C++ 草案有不同的文本,可能是对 C 标准的澄清。来自草案:1994 年 9 月 20 日

    17.3.3.1.2 全局名称
    ...
    以下划线和大写字母或另一个下划线 (2.8) 开头的每个名称都保留给实现以供任何使用

因此,显然“保留用于任何用途”的措辞是由 ANSI/ISO C90 委员会发明的,而 C++ 委员会几年后使用了更清晰的措辞,类似于准标准 K&R 书中的措辞。


C99 基本原理 V5.10 在 7.1.3 以下说明:

为实现者保留的还有以下划线开头的所有外部标识符,以及以下划线开头的所有其他标识符,后跟大写字母或下划线。这为编写库正常工作所需的众多幕后非外部宏和函数提供了名称空间。

这让委员会的意图十分明确:“reserved for any use”的意思是“reserved for the implementationor”。


同样值得注意的是,当前的 C 标准在 6.2.5 的其他地方有以下规范性文本:

也可能有实现定义的扩展有符号整数类型。38)

内容丰富的脚注 38 说:

38) 实现定义的关键字应具有为 7.1.3 中所述的任何用途保留的标识符的形式。

于 2018-09-17T14:44:37.013 回答
4

C 有多个上下文,其中一个符号可以有一个定义:

  • 宏名称的空间,
  • 宏参数的正式名称空间(该空间特定于每个类似函数的宏),
  • 普通标识符的空间,
  • 标签名称的空间,
  • 标签空间(此空间特定于每个功能),以及
  • 结构/联合成员的空间(此空间特定于每个结构/联合)。

“保留用于任何用途”是指兼容程序中的用户代码在上述任何上下文中都不能使用以下划线开头后跟大写字母或另一个下划线的1符号。与以下划线开头但后跟小写数字或数字的标识符进行比较。这属于以下划线开头的第二类标识符。用户代码可以将这些标识符用作宏参数的名称、标签或结构/联合成员的名称。

“保留用于任何用途”并不意味着实现不能使用此类符号。保留的目的是提供一个实现可以自由使用的名称空间,而不用担心实现定义的名称会与兼容程序中用户代码定义的名称冲突。


1该标准并不完全意味着“不能使用”。该标准鼓励以编程方式使用以双下划线开头的少量名称。例如,需要一个兼容的实现来定义__STDC_VERSION____FILE____LINE____func__。该标准的 2011 版本甚至给出了一个可能符合标准的程序的示例,该程序引用了__func__.

于 2018-09-17T15:26:18.613 回答
2

C 标准允许实现将他们认为合适的任何含义附加到保留标识符。当没有理由不这样做时,大多数实现将保留形式的未识别标识符与任何其他已识别标识符相同,因此允许类似:

#ifdef __ACME_COMPILER
#define near __near
#else
#define near
#endif

int near foo;

foo如果代码正在 Acme 编译器中处理(可能支持这样的事情),则使用限定符声明标识符__near,但也与不需要或受益于使用此类指令的其他编译器兼容。没有什么会禁止一个符合标准的实现定义__ACME_COMPILER和解释__near为“发射核导弹”,但是一个高质量的实现不应该像上面那样破坏代码。如果一个实现不知道__ACME_COMPILER应该是什么意思,将其视为任何其他未知标识符将允许它支持像上面这样的有用构造。

于 2018-09-17T21:33:22.440 回答
0

已经晚了几个月,但有一点仍然没有解决。

你的问题可以从相反的方向来看。该标准允许实现(如您所见)使用类似_Foo但更重要的符号,从而禁止实现使用foo. 后者保留供使用。

为了理解,为了讨论,假设未来的 C 标准引入了 new 关键字_Foo。假设的实现已经使用了这个符号,那么会发生什么?

回答:

  1. 起初,实施将尚未实施新标准。在实施之前,新标准缺乏实际效果。

  2. 后来,作为实施新标准的一部分,实施悄悄地更改_Foo_Bar.

没问题。

事实上,如果你这样想,你可以说标准保留这些词的方式几乎是它可以保留它们的唯一方式。

于 2019-02-27T13:03:41.553 回答