了解当宏间接扩展自身时 C 的预处理器的行为 [英] Understanding the behavior of C's preprocessor when a macro indirectly expands itself

查看:16
本文介绍了了解当宏间接扩展自身时 C 的预处理器的行为的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当我在处理一个充满宏技巧和魔法的大型项目时,我偶然发现了一个宏无法正确扩展的错误.结果输出为EXPAND(0)",但EXPAND被定义为#define EXPAND(X) X".,所以很明显输出应该是0".

While I was working on a big project full of macro tricks and wizardry, I stumbled upon a bug in which a macro was not expanding properly. The resulting output was "EXPAND(0)", but EXPAND was defined as "#define EXPAND(X) X", so clearly the output should have been "0".

没问题",我心想.这可能是一个愚蠢的错误,这里有一些令人讨厌的宏,毕竟有很多地方会出错".正如我所想的那样,我将行为不端的宏隔离到他们自己的项目中,大约 200 行,并开始研究 MWE 以查明问题.200 行变成了 150,然后又变成了 100,然后是 20、10……令我震惊的是,这是我最后的 MWE:

"No problem", I thought to myself. "It's probably some silly mistake, there are some nasty macros here, after all, plenty of places to go wrong". As I thought that, I isolated the misbehaving macros into their own project, about 200 lines, and started working on a MWE to pinpoint the problem. 200 lines became 150, which in turn became 100, then 20, 10... To my absolute shock, this was my final MWE:

#define EXPAND(X) X
#define PARENTHESIS() ()
#define TEST() EXPAND(0)
   
EXPAND(TEST PARENTHESIS()) // EXPAND(0)

4 行.

雪上加霜,几乎任何对宏的修改都会使它们正常工作:

To add insult to injury, almost any modification to the macros will make them work correctly:

#define EXPAND(X) X
#define PARENTHESIS() ()
#define TEST() EXPAND(0)

// Manually replaced PARENTHESIS()
EXPAND(TEST ()) // 0

#define EXPAND(X) X
#define PARENTHESIS() ()
#define TEST() EXPAND(0)

// Manually replaced TEST()
EXPAND(EXPAND(0)) // 0

// Set EXPAND to 0 instead of X
#define EXPAND(X) 0
#define PARENTHESIS() ()
#define TEST() EXPAND(0)

EXPAND(TEST PARENTHESIS()) // 0

但最重要也是最奇怪的是,下面的代码以完全相同的方式失败:

But most importantly, and most oddly, the code below fails in the exact same way:

#define EXPAND(X) X
#define PARENTHESIS() ()
#define TEST() EXPAND(0)
   
EXPAND(EXPAND(EXPAND(EXPAND(TEST PARENTHESIS())))) // EXPAND(0)

这意味着预处理器完全能够扩展EXPAND,但由于某种原因,它绝对拒绝在最后一步再次扩展它.

This means the preprocessor is perfectly capable of expanding EXPAND, but for some reason, it absolutely refuses to expand it again in the last step.

现在,我将如何在我的实际程序中解决这个问题既不存在也不存在.虽然一个解决方案会很好(即一种将令牌 EXPAND(TEST PARENTHESIS()) 扩展为 0 的方法),但我最感兴趣的是:为什么?为什么 C 预处理器会得出EXPAND(0)"的结论?在第一种情况下是正确的扩展,但在其他情况下不是?

Now, how I'm going to solve this problem in my actual program is neither here nor there. Although a solution would be nice (i.e. a way to expand the token EXPAND(TEST PARENTHESIS()) to 0), the thing I'm most interested in is: why? Why did the C preprocessor come to the conclusion that "EXPAND(0)" was the correct expansion in the first case, but not in the other ones?

虽然在 what C 预处理器会做(以及一些你可以用它做的魔法),我还没有找到一个解释如何它是如何做到这一点的,我想借此机会更好地了解预处理器是如何工作的,以及它在扩展宏时使用的规则.

Although it's easy to find resources on what the C preprocessor does (and some magic that you can do with it), I've yet to find one that explains how it does it, and I want to take this opportunity to understand better how the preprocessor does its job and what rules it uses when expanding macros.

因此,鉴于此:预处理器决定将最终宏扩展为EXPAND(0)"的原因是什么?而不是0"?

So in light of that: What is the reasoning behind the preprocessor's decision to expand the final macro to "EXPAND(0)" instead of "0"?

在阅读了 Chris Dodd 非常详细、合乎逻辑且措辞恰当的回答后,我做了任何人在相同情况下都会做的事情……尝试提出一个反例 :)

After reading Chris Dodd's very detailed, logical and well-put answer, I did what anybody would do in the same situation... try to come up with a counterexample :)

我炮制的是这个不同的 4-liner:

What I concocted was this different 4-liner:

#define EXPAND(X) X
#define GLUE(X,Y) X Y
#define MACRO() GLUE(A,B)

EXPAND(GLUE(MACRO, ())) // GLUE(A,B)

现在,知道 C 预处理器不是图灵完备的事实,以上内容不可能扩展到AB.如果是这种情况,GLUE 将展开 MACRO,而 MACRO 将展开 GLUE.这将导致无限递归的可能性,可能意味着 Cpp 的图灵完备性.可悲的是,对于那里的预处理器向导来说,上面的宏不扩展是一种保证.

Now, knowing the fact that the C preprocessor is not Turing complete, there is no way the above will ever expand to A B. If that were the case, GLUE would expand MACRO and MACRO would expand GLUE. That would lead to the possibility of unlimited recursion, probably implying Turing Completeness for the Cpp. So sadly for the preprocessor wizards out there, the above macro not expanding is a guarantee.

失败并不是真正的问题,真正的问题是:在哪里?预处理器在哪里决定停止扩展?

It failing is not really the problem, the real problem is: Where? Where did the preprocessor decide to stop the expansion?

分析步骤:

  • 第 1 步看到宏 EXPAND 并在参数列表 GLUE(MACRO, ()) 中扫描 X
  • 第 2 步将 GLUE(MACRO, ()) 识别为宏:
    • 第 1 步(嵌套)获取 MACRO() 作为参数
    • 第 2 步扫描它们但没有找到宏
    • 第 3 步插入宏体产生:MACRO ()
    • 第 4 步抑制 GLUE 并扫描 MACRO () 以查找宏,找到 MACRO
      • 第 1 步(嵌套)获取参数的空标记序列
      • 第 2 步扫描那个空序列并且什么都不做
      • 第 3 步插入宏体 GLUE(A,B)
      • 第 4 步扫描 GLUE(A,B) 中的宏,找到 GLUE.然而,它被抑制了,所以它就这样离开了.
      • step 1 sees the macro EXPAND and scans in argument list GLUE(MACRO, ()) for X
      • step 2 recognizes GLUE(MACRO, ()) as a macro:
        • step 1 (nested) gets MACRO and () as arguments
        • step 2 scans them but finds no macro
        • step 3 inserts into the macro body yielding: MACRO ()
        • step 4 suppresses GLUE and scans MACRO () for macros, finding MACRO
          • step 1 (nested) gets an empty token sequence for the argument
          • step 2 scans that empty sequence and does nothing
          • step 3 inserts into the macro body GLUE(A,B)
          • step 4 scans GLUE(A,B) for macros, finding GLUE. It is suppressed, however, so it leaves as is.
          • 第 1 步获取参数的 AB (oh no)
          • 第 2 步对它们没有任何作用
          • 第 3 步替换到正文中,给出 A B (well...)
          • 第 4 步扫描 A B 中的宏,但什么也没找到
          • step 1 gets A and B for the arguments (oh no)
          • step 2 does nothing with them
          • step 3 substitutes into the body giving A B (well...)
          • step 4 scans A B for macros, but finds nothing

          这将是我们的梦想.遗憾的是,宏扩展为 GLUE(A,B).

          Which would be our dream. Sadly, the macro expands to GLUE(A,B).

          所以我们的问题是:为什么?

          So our question is: Why?

          推荐答案

          宏扩展是一个复杂的过程,真正只有理解发生的步骤才能理解.

          Macro expansion is a complex process that is really only understandable by understanding the steps that occur.

          1. 当一个带有参数的宏被识别时(宏名记号后跟(记号),以下记号直到匹配的)被扫描和分割(, 标记).发生这种情况时不会发生宏扩展(因此 s 和 ) 必须直接存在于输入流中,不能存在于其他宏中).

          1. When a macro with arguments is recognized (macro name token followed by ( token), the following tokens up to the matching ) are scanned and split (on , tokens). No macro expansion happens while this is happening (so the ,s and ) must be present in the input stream directly and cannot be in other macros).

          名称出现在宏体中的每个宏参数###之前或在之后## 是预扫描的";用于扩展宏 - 完全在参数中的任何宏都将在替换到宏主体之前递归扩展.

          Each macro argument whose name appears in the macro body not preceeded by # or ## or followed by ## is "prescanned" for macros to expand -- any macros entirely within the argument will be recursively expanded before substituting into the macro body.

          生成的宏参数标记流被替换到宏的主体中.### 操作中涉及的参数会根据步骤 1 中的原始解析器标记进行修改(字符串化或粘贴)和替换(这些不会发生步骤 2).

          The resulting macro argument token streams are substituted into the body of the macro. Arguments involved in # or ## operations are modified (stringized or pasted) and substituted based on the original parser tokens from step 1 (step 2 does not occur for these).

          再次扫描生成的宏主体令牌流以查找要扩展的宏,但忽略当前正在扩展的宏.此时,输入中的其他标记(在步骤 1 中扫描和解析的内容之后)可能包含在任何识别的宏中.

          The resulting macro body token stream is scanned again for macros to expand, but ignoring the macro currently being expanded. At this point further tokens in the input (after what was scanned and parsed in step 1) may be included as part of any macros recognized.

          重要的是发生了两种不同的递归扩展(上面的第 2 步和第 4 步),并且只有第 4 步中的一个会忽略同一宏的递归宏扩展.步骤 2 中的递归扩展不会忽略当前宏,因此可以递归扩展.

          The important thing is that there are TWO DIFFERENT recursive expansions that occur (step 2 and step 4 above) and ONLY the one in step 4 ignores recursive macro expansions of the same macro. The recursive expansion in step 2 DOES NOT ignore the current macro, so can expand it recursively.

          因此,对于上面的示例,让我们看看会发生什么.对于输入

          So for your example above, lets see what happens. For the input

          EXPAND(TEST PARENTHESIS())
          

          • 第 1 步看到宏 EXPAND 并在参数列表 TEST PARENTHESIS() 中扫描 X
          • 第 2 步不能将 TEST 识别为宏(没有后面的 (),但可以识别 PARENTHESIS):
            • 第 1 步(嵌套)获取参数的空标记序列
            • 第 2 步扫描那个空序列并且什么都不做
            • 第 3 步插入到宏体 () 中,结果是:()
            • 第 4 步扫描 () 中的宏,但未找到任何宏
              • step 1 sees the macro EXPAND and scans in argument list TEST PARENTHESIS() for X
              • step 2 does not recognize TEST as a macro (no following (), but does recognize PARENTHESIS:
                • step 1 (nested) gets an empty token sequence for the argument
                • step 2 scans that empty sequence and does nothing
                • step 3 inserts into the macro body () yielding just that: ()
                • step 4 scans () for macros and doesn't find any
                  • 第 1 步获取参数的空序列
                  • 第 2 步不执行任何操作
                  • 第 3 步替换为给出 EXPAND(0)
                  • 的正文
                  • 第 4 步递归扩展了它,抑制了 TEST.此时,EXPANDTEST 都被抑制(由于处于第 4 步扩展中),因此没有任何反应
                  • step 1 gets an empty sequence for the argument
                  • step 2 does nothing
                  • step 3 substitutes into the body giving EXPAND(0)
                  • step 4 recursive expands that, suppressing TEST. At this point, both EXPAND and TEST are suppressed (due to being in the step 4 expansion), so nothing happens

                  您的其他示例 EXPAND(TEST()) 不同

                  • 第1步EXPAND被识别为宏,TEST()被解析为参数X
                  • 第 2 步,递归解析此流.请注意,由于这是第 2 步,因此 EXPAND 不会被阻止
                    • 第 1 步 TEST 被识别为具有空序列参数的宏
                    • 第 2 步 - 无(空标记序列中没有宏)
                    • 第 3 步,代入正文给出 EXPAND(0)
                    • 第四步,TEST被抑制,结果递归展开
                      • 第1步,EXPAND被识别为宏(记住,此时只有TEST被第4步递归抑制——EXPAND处于第 2 步递归中,因此不会被抑制),以 0 作为其参数
                      • 第 2 步,0 被扫描,没有任何反应
                      • 第 3 步,替换到正文中给出 0
                      • 第 4 步,0 再次扫描宏(再次没有任何反应)
                      • step 1 EXPAND is recognized as a macro, and TEST() is parsed as the argument X
                      • step 2, this stream is recursively parsed. Note that since this is step 2, EXPAND is NOT SUPPRESSED
                        • step 1 TEST is recognized as a macro with an empty sequence argument
                        • step 2 -- nothing (no macros in an empty token sequence)
                        • step 3, substituted into the body giving EXPAND(0)
                        • step 4, TEST is suppressed and the result recursively expanded
                          • step 1, EXPAND is recognized as a macro (remember, at this point only TEST is suppressed by step 4 recursion -- EXPAND is in the step 2 recursion so is not suppressed) with 0 as its argument
                          • step 2, 0 is scanned and nothing happens to it
                          • step 3, substitute into the body giving 0
                          • step 4, 0 is scanned again for macros (and again nothing happens)

                          所以这里的最终结果是0

                          这篇关于了解当宏间接扩展自身时 C 的预处理器的行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆