代码之家  ›  专栏  ›  技术社区  ›  Jon Purdy

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

  •  1
  • Jon Purdy  · 技术社区  · 14 年前

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

    2. 编译器必须按照顺序执行编译的各个阶段(预处理、编译、链接)。

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

    4. 因此,程序可以使用保留标识符 :

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

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

      3. GNUC 等人)

    5 回复  |  直到 14 年前
        1
  •  6
  •   John Marshall    14 年前

    (关于这个问题的评论解释了我们所说的 保留标识符 在C99第7.1.3节的意义上,即标识符匹配 /^_[A-Z_]/ /^_/ 在文件范围内, /^str[a-z]/

    它们不是保留在编译器(的任何特定阶段)被期望诊断它们的误用的意义上。相反,它们是保留的,因为如果您愚蠢到自己(错误地)使用它们,那么如果您的程序在以后停止工作或停止编译,您就不必抱怨。

    我们都看到过当知识量非常有限的人查看系统标题内部,然后编写自己的标题保护时会发生什么:

    #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() 在以后的修订中。我决定 一串 ip ,不管它是什么,对于将来要添加的字符串操作来说,这是一个不太可能的名称--因此在发生这种不太可能的事件时,当我到达它时,我将跨越这座桥。从技术上讲,我是在援引未定义的行为,但我不希望被咬。

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

    这符合你的4.1,4.2,4.3(如果我理解你的意图在最后一个)。但是,如果 memmove 作为宏(通过7.1.4/1)另外实现,该宏以 memcpy ,那你就有麻烦了。

        2
  •  2
  •   Jens Gustedt    14 年前

    我想,这个故事要比这复杂得多,至少对美国来说是这样

    例3。如果是假的 defined 即使在预处理阶段,令牌也是保留的,伪宏 __LINE__ , __func__

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

    • 某些标识符是显式的 为外部符号保留,例如 setjmp .
    • 以开头的标识符 下划线或大写字母是
    • 以下划线开头的标识符 然后是小写字母 在文件范围内禁止,因为它们 可能指外部符号。他们

    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)
    

    (顺便说一句,任何人都不要使用这个宏,它编译和执行它的外观,但有一些角的情况下,让它爆炸。)

        3
  •  2
  •   Michael Burr    14 年前

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

    我不知道你所说的“意识”是什么意思,但我认为你不一定能假设这一点-7.1.3说

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

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

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

    当然,我认为在相当多的情况下,您可以使用保留的标识符—实现不会特意给您带来问题。很多代码都使用了保留的名称,我猜实现不会在没有足够理由的情况下破坏这些代码。但是,如果您没有实现编译器工具链,最好完全避免使用这个名称空间。

        4
  •  1
  •   David Thornley    14 年前

    标识符,如 _UNDERSCORE_CAP double__underscore _File <stdio.h> ,这就是预订的目的。如果用户使用一个,这是一个潜在的问题。

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

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

        5
  •  0
  •   nategoose    14 年前

    如果你问你能不能 #define if while 使你的代码不可读,那么是的。这是模糊C比赛中的常见做法。但这实际上与你的4.2背道而驰。

    __LINE__ __FILE__