【问题标题】:Is it possible to define another preprocessor directive?是否可以定义另一个预处理器指令?
【发布时间】:2013-08-27 20:16:52
【问题描述】:

我一直在查看 code golf 并想到尝试此代码:

#define D #define 添加此行后,一切正常,但是我将其扩展为:

#define D #define
D VALUE

在这里我得到了 5 个编译错误。如果我将D 更改为#define 一切都很好,谁能解释一下,为什么这段代码是非法的?

注意:我使用的是 VS2008 编译器。

编辑:经过一些回答,我发现我需要提供编译错误列表:

  1. 错误 C2121:“#”:无效字符:可能是宏扩展的结果
  2. 错误 C2146:语法错误:缺少 ';'在标识符“VALUE”之前
  3. 错误 C4430:缺少类型说明符 - 假定为 int。注意:C++ 不支持 default-int
  4. 错误 C2144:语法错误:'void' 应以';'开头
  5. 错误 C4430:缺少类型说明符 - 假定为 int。注意:C++ 不支持 default-int

第一个错误显示D不仅仅是define,还包括#

【问题讨论】:

  • 由于它可能依赖于实现,请说明您正在使用哪个编译器。
  • "为什么这个代码是非法的?" - 因为你不能重新定义预处理器指令。
  • @H2CO3 我能够重新定义它,因为它第一次工作,但是在使用该定义后我得到了编译错误。
  • @user2623967 C 预处理器没有定义元语法。
  • @user2623967:不,您无法重新定义它。除非您使用预处理器宏,否则它不会被扩展,因此不会被检查。在这种特殊情况下,扩展并没有按照您的想法进行。

标签: c++ c c-preprocessor preprocessor-directive


【解决方案1】:

C 2011 (N1570) 6.10.3.4 3:“生成的完全用宏替换的预处理令牌序列即使类似于一个预处理指令,也不会被处理,......”

C++ 2010 (N3092) 16.3.4 [cpp.rescan] 3 具有完全相同的文本。

【讨论】:

  • 这是正确的答案。 C 语言标准规定了程序源代码翻译的确切操作顺序。
  • 这个“即使它像一个”文本可以追溯到 1989 年的 ANSI C 标准(很可能是在那之前的草案)。
  • N3092 不是标准……尽管 C++98、C++11 和所有曾经或将要成为的 C++ 标准草案中的文本无疑是相同的。
【解决方案2】:

这个代码是非法的,因为语言规范说它是非法的。根据 C 和 C++ 预处理器规范,您使用预处理器构建的任何代码都不会被解释为另一个预处理器指令。简而言之,您不能使用预处理器构建预处理器指令。时期。

(另外,您不能使用预处理器构建 cmets。)

【讨论】:

    【解决方案3】:

    看起来你的预处理器正在做你想要的替换,但你可能不会得到你想要的行为——预处理器通常只是一个单遍操作。示例(使用 clang,但您应该能够通过使用适当的 VS2008 标志来重现):

    $ cat example.c 
    #define D #define
    D VALUE
    $ cc -P -E example.c 
    
     #define VALUE
    

    #define VALUE 直接进入编译器,编译器不知道如何处理它——毕竟它是一个预处理器指令。 Clang 的错误,供参考,与您的类似:

    $ cc -c example.c 
    example.c:2:1: error: expected identifier or '('
    D VALUE
    ^
    example.c:1:11: note: expanded from macro 'D'
    #define D #define
              ^
    1 error generated.
    

    【讨论】:

    • 预处理器是否是单遍操作无关紧要。预处理器不需要对文件进行一次以上的扫描即可重新处理各个行;它只能根据需要多次处理每一行。事实上,它确实这样做了:重复执行宏替换。宏替换后不处理预处理器指令的原因是语言标准不允许这样做。
    • 是的,同意。不知道官方的规定。
    【解决方案4】:

    这是行不通的,因为预处理是一次性执行的。例如,考虑下面的代码:

    #define MYDEFINEWEIRD #define
    
    MYDEFINEWEIRD N 6
    
    int main() {
    
      return 0;
    }
    

    预处理后,您的代码将如下所示:

     #define N 6
    int main() {
    
      return 0;
    }
    

    并且“#define”不是 C 或 C++ 上的有效语法。此外,由于不会处理生成的预处理器指令,因此它不会解析代码中对“N”宏的后续引用。

    只是为了好玩,您可以使用 g++/gcc 从命令行调用预处理器两次。考虑下一个代码(define.cpp):

    #include <iostream>
    
    #define MYDEFINEWEIRD #define
    MYDEFINEWEIRD N 6
    
    using namespace std;
    
    int main() {
      cout << N << endl;
      return 0;
    }
    

    那么你可以这样做:

    $ g++ -E define.cpp | g++ -o define -x c++ - && ./define
    

    会输出:

    6
    

    【讨论】:

    • “预处理是一次性执行的”——不,实际上不是——这不是错误的原因。
    • @EricPostpischil 嗯,没错,它确实说它是单通道操作。但事实并非如此,不是吗? #define FOO BAR,然后是 #define BAR 0,然后是 return FOO; 扩展为 return 0;
    • 刚刚看到@Eric Postpischil 的回答,不知道。我看到了预处理的文件,它看起来像一个单通道:-p
    • C 语言被描述为在多个翻译阶段进行处理,其中大约四个是预处理阶段。这些可以是单独的通道或折叠在一起。
    【解决方案5】:

    预处理器眼中的代码行要么是预处理器语句(因此没有对它们进行任何替换),要么是普通的文本语句(并且已经完成了替换)。您不能两者兼而有之,因此一旦替换了“ D”,它只会查看是否还有要替换的宏。由于没有,它只是在 C++ 代码中保留“#define”,然后 C++ 编译器在看到它时会出错(因为“#define”不是有效的 C++ 代码)。

    所以再说明一下,这是预处理器的无效代码:

    #define D define
    #D value
    

    因为预处理器不会对预处理器语句进行任何宏替换,并且“#D”不是公认的预处理器命令。还有这个:

    #define D #define
    D value
    

    此 C++ 代码中的结果:

    #define value
    

    这是无效的,因为预处理器已经运行完毕。

    【讨论】:

      【解决方案6】:

      查看 16 [cpp] 第 1 段中的语法,replacement-listpp-tokens 组成,其中可能包括产生式 # no-directive 在同一段的第 2 段中描述为

      非指令不应以列表中出现的任何指令名称开头。

      也就是说,某种形式的东西

      #define NAME # define
      

      碰巧是非法的!另请注意,此上下文中的 # 确实将下一个单词转换为字符串:# 后面的引用仅在 # 后面紧跟宏参数名称时发生一个函数式的宏。

      【讨论】:

      • 句子“A non-directive shall not begin...”中的non-directive 语法符号指的是# non-directive 行。因此,它只是将看起来像# some-directive 的行与看起来像# non-directive 的行区分开来。这不会使 #define NAME # define 成为无效的预处理器标记序列。
      猜你喜欢
      • 1970-01-01
      • 2010-09-23
      • 1970-01-01
      • 2022-11-11
      • 1970-01-01
      • 2020-04-07
      • 1970-01-01
      • 1970-01-01
      • 2015-07-12
      相关资源
      最近更新 更多