在编译的哪个阶段保留标识符?

时间:2010-09-28 19:30:59

标签: c++ c compiler-construction reserved-words

在这里工作只是一点点好奇心。在处理危险的事情时,我开始考虑各种编译器及其相关标准库的实现。这是我思想的进展:

  1. 某些类标识符保留用于在C ++和C中实现。

  2. 编译器必须执行编译阶段(预处理,编译,链接),就好像它们是按顺序执行一样。

  3. C预处理器不知道标识符的保留状态。

  4. 因此,当且仅当:

    时,程序可以使用保留标识符
    1. 使用的保留标识符都是预处理程序符号。

    2. 预处理结果不包含保留标识符。

    3. 标识符与编译器预定义的符号不相冲突(GNUC等。)

  5. 这有效吗?我对第3点和第4.3点不确定。此外,有没有办法测试它?

5 个答案:

答案 0 :(得分:5)

(对该问题的评论解释说,我们在谈论C99第7.1.3节中的保留标识符,即在任何地方匹配/^_[A-Z_]/的标识符,{{1} }在文件范围内,/^_/与外部链接等等。所以这里至少是你要问的一部分......)

在编译器的(任何特定阶段)预期诊断其滥用的意义上,它们不是保留的。相反,它们是保留的,如果你自己愚蠢地(错误地)使用它们,如果你的程序停止工作或者在以后停止编译,你就不会抱怨。

我们都看到当只有大量知识的人看到系统标题然后编写自己的标题保护时会发生什么:

/^str[a-z]/

他们正在调用未定义的行为,但没有任何事情将其诊断为“错误:最终用户代码使用的保留标识符”。相反,他们很幸运,一切都很好;但有时它们会与实施所关注的标识符相冲突,并且会发生令人困惑的事情。

同样,我经常有一个名为#ifndef _MYHEADER_H #define _MYHEADER_H // ... #endif 左右的外部可见函数:

strip()

通过阅读C99的7.1.3,7.26和7.26.11,这会调用未定义的行为。但是我决定不关心这个。标识符不是保留的,因为预计今天会发生任何不良事件,但是因为标准保留了自己在未来版本中发明新标准char *strip(char *s) { // remove leading whitespace } 例程的权利。而且我已经确定我认为 string - str-ip(),无论可能是什么,都是一个不太可能的名称,以便将来添加一个字符串操作 - 所以在不太可能的事件中碰巧的是,当我到达那里时,我将穿过那座桥。从技术上讲,我正在调用未定义的行为,但我不希望被咬掉。

最后,反对你的观点4:

ip

这符合您的4.1,4.2,4.3(如果我理解您对最后一个的意图)。但是,如果将#include <string.h> #define memcpy(d,s,n) (my_crazy_function((n), (s))) void foo(char *a, char *b) { memcpy(a, b, 5); // intends to invoke my_crazy_function memmove(a, b, 5); // standard behaviour expected } 另外实现为以memmove编写的宏(通过7.1.4 / 1),那么您将遇到麻烦。

答案 1 :(得分:2)

这个故事比这更复杂,我想,至少对于来说,当且仅当。我记得C99:

例如3.为假,即使在预处理阶段也保留defined令牌,也可能不会重新定义__LINE____func__等伪宏。

然后,标识符的保留取决于范围。

  • 有些标识符是明确的 保留用于外部符号,例如 setjmp
  • 标有开头的标识符 强调,然后另一个 下划线或大写字母 你应该在C处到处保留 永远不要碰他们,即使是 预处理器。
  • 以下划线开头的标识符 然后是一个小写字母 因为他们在文件范围内被禁止 可以指外部符号。他们 可以在功能范围内自由使用。

4.2也不完全正确。首先,只有未定义的行为(又名非常邪恶)才能在以下条件下定义一个具有关键字作为其名称的宏:

  

包含标准标题   宏的定义与名称相同   关键字(7.1.2)。

然后,在其扩展中包含其自己名称的宏是“安全的”,因为保证扩展不是递归的。以下内容有效,但不推荐:

#define if(...)                                         \
for(int _i = 0; _i < 1; ++_i)                           \
  for(int _cond = (__VA_ARGS__);                        \
      _i < 1;                                           \
      printf("line %d val %d\n", __LINE__, _cond),      \
        ++_i)                                           \
    if(_cond)

(顺便说一句,不要有人使用那个宏,它会编译并做它看起来像什么,但是有一些让它爆炸的角落情况。)

答案 2 :(得分:2)

  

C预处理器不知道标识符的保留状态。

我不确定“意识到”是什么意思,但我认为你不一定会认为这是 - 7.1.3说

  

所有以下划线开头的大写字母或另一个下划线的标识符始终保留供任何使用

预处理器(或编译器)实现可以将这些保留的标识符用于任何适合它的目的 - 如果您滥用这些标识符,则不需要警告您。

我建议“当且仅当”标准(例如一组预定义的宏)或实现在其文档中说明时,程序才可以使用保留标识符。

当然,我认为你可以使用在很多情况下保留的标识符 - 实现并不会让你的问题彻底解决。很多代码都使用了保留的名称,我猜想实现不会在没有足够理由的情况下破坏代码。但是,如果您没有实现编译器工具链,那么最好完全避免使用该命名空间。

答案 3 :(得分:1)

_UNDERSCORE_CAPdouble__underscore等标识符保留供实现使用,因为它认为合适。如果实现使用它们,这不是问题,例如在_File中有<stdio.h>标识符或宏,这就是预留的内容。如果用户使用它,这是一个潜在的问题。

因此,为了诊断这一点,编译器必须跟踪标识符的来源。仅检查不在<angle_bracket_files.h>中的代码是不够的,因为这些代码可以定义可能使用的宏,并且可能使用实现保留字扩展为某些内容。例如,isupper可能会在<ctype.h>中定义为

#define isupper(x) (_UPPER_BIT & _CHAR_TRAITS[x])

或其他一些。 (自从我看到基于上述内容的定义以来已经很长时间了。)

因此,为了跟踪这一点,预处理器必须维护哪些宏来自那里的记录。跟踪会使预处理器大大复杂化,编译器编写者似乎认为没有相应的增益。

答案 4 :(得分:0)

如果您询问是否可以#define if while并且使您的代码无法阅读,那么是的。这是混淆的C竞赛中的常见做法。但这实际上与你的4.2相反。

对于像GNUC这样的东西,它们是预定义的,但你通常可以重新定义它们并取消它们。这样做并不是一个好主意,但你可以。更有趣的是重新定义或取消定义__LINE____FILE__和类似的预处理器符号(b / c它们会自动更改)。