“保留供任何用途”是什么意思?

54

注意:这是一个问题,尽管我加入了,以防一些C++专家能够提供C++使用不同措辞的原因或历史背景。


在C标准库规范中,我们有这样的规范文本,C17 7.1.3 保留标识符(强调是我的):
  • 所有以下划线和大写字母或另一个下划线开头的标识符始终保留供任何用途使用
  • 所有以下划线开头的标识符始终保留供普通名称空间和标记名称空间中的文件作用域标识符使用。
现在我一直在阅读各种著名的C专家在SO上的答案,他们声称编译器或标准库使用带下划线+大写字母或双下划线的标识符是可以的。
“保留供任何用途使用”难道不意味着除了C语言本身的未来扩展之外,实现都不允许使用它们吗?
而上述第二个关于单个前导下划线的短语似乎是针对实现的?
总的来说,C标准的写法期望编译器厂商/库实现者成为典型的读者,而不是应用程序员。
值得注意的是,C++的措辞非常不同:
  • 任何包含双下划线 (__) 或以下划线开头、后面紧跟大写字母的名称 (2.11) 都被实现保留,用于任何目的

(参见 C++标识符中使用下划线的规则是什么?)

这可能是关于C和C++混淆了,两种语言在这方面有所不同吗?


2
C语言中没有命名空间支持,这使得对所有人都更加困难,甚至包括ISO委员会。他们必须要有一种方法来添加一个标识符而不破坏整个世界。 - Hans Passant
我认为,“保留供任何用途”是针对实现和标准库使用的。当然,库的实现者应该足够了解标准版本,以便知道每个保留名称,因此不会出现名称冲突。也许这意味着可以扩展到其他系统库,例如 POSIX 或 Windows。但是,是的,我认为缺少自定义命名空间是这种含糊用语的原因之一。有时候我希望他们能够打破遗留代码的“世界”。它仍然可以编译为设计的版本或进行重新设计。例如 Python 就是这样做的。 - too honest for this site
10
请不要删除C++标签。问题的最后一部分涉及C和C++的区别,这对于了解来说是很有趣的。我也会尝试自己进一步研究这部分内容。 - Lundin
6个回答

34
在C标准中,“保留”一词的含义由7.1.3p2定义,在你引用的项目符号列表下面立即出现:
“没有其他标识符被保留。如果程序在其保留上下文中声明或定义标识符(除了允许 7.1.4 中的情况之外),或者将保留标识符定义为宏名称,则行为未定义。”
加粗部分是我的:保留的标识符对程序施加限制,而不是实现。因此,常见的解释-保留的标识符可被实现用于任何目的-在C中是正确的。
我并没有跟上C++标准的更新,因此不再感到有资格去解释它。

我也读了那部分,但我认为这么说这就是“保留标识符”的定义有点牵强。人们可以将其解释为7.1.3 §1是实现的限制,§2是程序的限制。我也想知道为什么C++用不同的措辞表达这个概念。这是否是C++委员会在某个时候添加的澄清(也许有人和我一样困惑),但这并没有回到C语言中? - Lundin
@Lundin 是的,我认为这只是一个非常早期的澄清(已经存在于1994年的草案中)。不过,我很惊讶地看到这个改变是在文字进一步更新以“包含双下划线”的每个名称保留之前进行的,我原以为这个修改早就应该随着那个段落的编辑而被进行了。 - user743382
@hvd 这很有趣,也许你可以将引用作为问题第二部分的答案发布?我只能挖掘到C++98,那里进行了更正。至于C语言,文本始终保持不变,一直延续到C89草案。 - Lundin
@Lundin 我没有任何实际的证据来支持这个想法,即除了这个答案已经说明的(即在C中,例如__foo也被保留供实现使用),没有意图改变其含义,但如果zwol想将其纳入这个答案中,它是这份1994年的草案,[lib.global.names]p3,其中写道:“以下划线和大写字母或另一个下划线(2.8)开头的每个名称都保留给实现用于任何用途。” - user743382
所以...一切似乎表明,“保留供任何用途”确实意味着“保留供实现使用”。事实上,C99的解释说明了这一点。我将我的研究作为一个单独的答案发布了出来。 - Lundin

16

虽然标准主要是为了指导实现者而编写的,但它也作为程序符合规范的描述以及其效果的说明。这是因为符合标准的编译器的基本定义是对任何符合标准的程序执行正确操作:

  

严格符合规范的程序应仅使用国际标准中指定的语言和库功能……符合要求的托管实现应接受任何严格符合规范的程序。

单独阅读上述内容,将极大地限制编译器的扩展。例如,仅根据该条款,编译器不应定义任何自己的保留字。毕竟,某个特定编译器可能想要保留的任何给定单词都可能出现在严格符合规范的程序中,迫使编译器采取行动。

然而,标准继续说道:

  

符合规范的实现可以具有扩展(包括其他库函数),前提是它们不会改变任何严格符合规范程序的行为。

这是关键。编译器扩展需要以这样一种方式编写,即它们影响非符合规范的程序(包括包含未定义行为或根本不应编译的程序),从而使它们能够编译并执行额外的有趣操作。

因此,定义“保留标识符”的目的,当语言实际上不需要这些标识符时,是为了通过提供一些使程序非一致的东西,为实现提供一些额外的灵活性。编译器能够识别例如__declspec作为声明的一部分的原因是将__declspec放入声明中在其他情况下是非法的,因此编译器被允许做任何它想做的事情!
因此,“保留供任何使用”的重要性在于,它不会让人对编译器处理这些标识符的能力产生任何疑问,编译器可以将这些标识符视为具有任何意义。未来的兼容性是一个相对较远的问题。
C++标准以类似的方式工作,尽管它对策略有点更明确:
“符合规范的实现可以具有扩展(包括额外的库函数),只要它们不改变任何良好形成程序的行为。实现必须诊断使用此国际标准不合规的扩展的程序。”然而,一旦进行了这样的诊断,它们就可以编译和执行此类程序”。

我怀疑措辞上的差异是由于C++标准更清楚地说明了扩展应该如何工作。尽管如此,C标准中没有阻止实现执行相同的操作。(我们基本上都忽略编译器每次使用__declspec时警告的要求。)


这是另一个相关的问题,我认为:如果编译器被允许在标准头文件中倾倒它们的非标准内容,那么它们可能会给它命名什么。请参见https://dev59.com/f13Va4cB1Zd3GeqPCaEv。 - Lundin
1
@Lundin 我不认为这是特别棘手的问题:包含标准头文件的程序本身并不能使编译器有权保留额外的标识符,但如果包括了标准头文件,编译器可能会决定仅允许特定的非标准结构。相比之下,在其他头文件中定义标准事物...我不确定那个。 - Sneftel

15

关于C语言和C++语言的用词差异,以下是我个人的一些研究成果:

...专为库函数使用的名称以下划线开头,这样它们就不太可能与用户程序中的名称发生冲突。

  • K&R第二版增加了附录B,涉及标准库,其中我们可以阅读到:

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

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

  • 然而,最早的C++草案有不同的文本,正如@hvd指出的那样,可能是对C标准的澄清。来自DRAFT: 20 September 1994的引用:

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

显然,“reserved for any use”一词是由ANSI / ISO C90委员会发明的,而C ++委员会几年后使用了更清晰的措辞,类似于预标准K& R书中的措辞。


C99理据V5.10在7.1.3以下说:

对于实现者而言,所有以下划线开头的外部标识符以及以下划线和大写字母或下划线开头的所有其他标识符都保留。 这为编写库需要正确运行的许多幕后非外部宏和函数提供了名称空间。

这使委员会的意图非常清楚:“保留以备任何用途”意味着“保留给实现者使用”。


值得注意的是,当前的C标准在6.2.5中有以下规范文本:

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

其中信息脚注38说:

  1. 实现定义的关键字应具有“保留以备任何用途”的标识符形式,如7.1.3所述。

1
回复:“‘保留供任何用途’意味着‘保留供实现者使用’”:更准确地说,“保留”意味着“保留给实现”,即“禁止编程代码使用”。 “保留供任何用途”意味着“禁止编程代码用于任何用途”。 - ruakh
这个确切的答案包括一个引用短语“保留给实现用于任何用途”。你肯定不是在争论这意味着“保留给实现者用于实现”吧? - ruakh
@ruakh,你的评论是回答的总结吗?我把它看作是解释含义的尝试,所以才问了你要源。不用理我。 - Lundin
这不是答案的摘要,而是对答案中一个小错误的更正。更新:好的,我希望你不介意,我刚刚编辑了答案以纠正错误。 - ruakh
@ruakh 我介意,因为这个编辑是不正确的。术语“reserved”并没有正式定义,它也用于关键字等,这是另一回事。此外还有“保留存储”和“保留供未来标准化使用”。 - Lundin
我理解你的观点,但是你的回滚也是不正确的,因为(正如你的答案所示),存在“保留给实现的任何用途”,因此“保留任何用途”与“保留给实现”并不是同义词。 - ruakh

5

C语言中,符号的定义可以存在多种上下文环境中:

  • 宏名称空间
  • 函数样式宏的形式参数名称空间(该空间特定于每个函数样式宏)
  • 普通标识符名称空间
  • 标记名称空间
  • 标签名称空间(该空间特定于每个函数)
  • 结构体/联合体成员名称空间(该空间特定于每个结构体/联合体)

"保留供任何用途"意味着在上述任何一个名称空间中,符号名以下划线后跟大写字母或另一个下划线开头的符号不能被符合规范的程序中的用户代码使用。
与此相比,以单个下划线开头但后面跟小写数字或数字的标识符属于第二类下划线开头的标识符。用户代码可以将这些标识符用作宏参数的名称、标签或结构/联合体成员的名称。

"保留供任何用途"并不意味着实现不能使用这些符号。保留的目的是提供一个命名空间,使得实现可以自由地使用名称而不必担心会与符合规范的程序中的用户代码定义的名称发生冲突。


1标准并不完全意味着"不能使用"。标准鼓励程序化地使用少量以双下划线开头的名称。例如,符合规范的实现需要定义__STDC_VERSION____FILE____LINE____func__。甚至2011年版本的标准给出了一个引用__func__的假定符合规范的程序示例。


2

C标准允许实现者将任何意义附加到保留标识符上。当没有其他原因时,大多数实现将未被识别的保留形式的标识符视为任何其他被识别的标识符一样,从而允许像这样的内容:

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

int near foo;

如果代码正在使用支持__near限定符的Acme编译器进行处理,则可以使用该限定符来声明标识符foo,但还要与其他不需要或不能从此指令中受益的编译器兼容。并没有什么可以阻止一个符合规范的实现定义__ACME_COMPILER并将__near解释为“发射核导弹”,但是高质量的实现不应该费劲地破坏上述代码。如果实现不知道__ACME_COMPILER的含义,将其视为任何其他未知标识符将允许它支持像上面那样有用的结构。


我非常清楚这一点。问题是,“保留供任何用途”一词是否意味着保留供任何用途,还是仅限于实现中使用。 - Lundin
@Lundin:我认为标准的维护者应该尽力避免定义与已知实现中使用的任何名称冲突的名称,但我认为这更多是常识问题,而不是正式定义的政策。 - supercat

0

虽然已晚了几个月,但还有一个问题其他人没有解决。

你的问题可以从相反的方向来看。标准允许实现(正如你所观察到的)使用像 _Foo 这样的符号,更重要的是,因此禁止实现使用 foo。后者保留给你的使用。

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

答案:

  1. 首先,实现尚未实现新标准。在实施之前,新标准缺乏实际效果。

  2. 稍后,作为实施新标准的一部分,实现悄悄地将每个_Foo更改为_Bar

没问题。

实际上,如果你从这个角度考虑,你可以说标准保留这些单词的方式几乎是它能够保留它们的唯一方式。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接