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

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

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

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

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

  4. 因此, 当且仅当以下情况时,程序可以使用保留标识符:

    1. 使用的保留标识符是所有预处理器符号。

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

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

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

(关于这个问题的评论解释说我们在讨论C99第7.1.3节中的保留标识符 ,即标识符匹配/^_[A-Z_]/ where, /^_/在文件范围内, /^str[az]/有外部联动等等所以这是我对你问的至少一部分的猜测……)

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

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

 #ifndef _MYHEADER_H #define _MYHEADER_H // ... #endif 

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

同样,我经常有一个名为strip()的外部可见函数:

 char *strip(char *s) { // remove leading whitespace } 

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

最后,反思你的观点4:

 #include  #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 } 

这符合您的4.1,4.2,4.3(如果我理解您对最后一个的意图)。 但是,如果将memmove另外实现为以memcpy编写的宏(通过7.1.4 / 1),那么您将遇到麻烦。

我认为,这个故事比这更复杂,至少对于if和only if 。 我记得C99:

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

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

  • 一些标识符明确地保留用于外部符号,例如setjmp
  • 在C中随处可见以下划线开头,然后是另一个下划线或大写字母的标识符。即使使用预处理器,也不应该触摸它们。
  • 以下划线开头然后以小写字母开头的标识符在文件范围内被禁止,因为它们可能引用外部符号。 它们可以在function范围内自由使用。

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) 

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

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

我不确定“意识到”是什么意思,但我认为你不一定能想到这一点 – 7.1.3说

所有以下划线或大写或下划线开头的标识符始终保留用于任何用途

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

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

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

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

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

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

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

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

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

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