用于在宏扩展期间跟踪 C 预处理器执行的工具?

Posted

技术标签:

【中文标题】用于在宏扩展期间跟踪 C 预处理器执行的工具?【英文标题】:Tool for tracing C preprocessor execution during macro expansion? 【发布时间】:2020-10-28 09:18:30 【问题描述】:

有没有办法逐步打印 C 预处理器在扩展宏时正在做什么?

例如,我会给它一些 C 语言文本(例如:.h 文件)进行预处理。为了演示,这里举个简单的例子:

// somefile.h
#define q r
#define bar(x,z) x ## z
#define baz(y) qux ## y
#define foo(x,y) bar(x, baz(y))

到目前为止,这只是建立一个定义表。

接下来是要详细展开的文本。对于这个演示,我期望工作流/过程/输出是这样的:

$ magical_cpp_revealer  somefile.h

Please enter some preprocessor text to analyse:
> foo(baz(p),q)

Here are the resulting preprocessor calculations:
,----.----.---------------------------.-----------------------------------------
|Step|Exp#|  Expression               |  Reason
|====|====|===========================|=========================================
| 00 | 00 |  foo(baz(p),q)            |  Original tokens.
| 01 |    |                           |  Definition found for 'foo': `foo(x,y)` = "bar(x, baz(y))"
| 02 | 01 |  bar(x, baz(y))           |  'foo' begins expansion. Original tokens shown.
| 03 |    |                           |  'foo' Stage 1: Raw parameter replacements elided: no # or ## operators present.
| 04 |    |                           |  'foo' Stage 2: Stringification elided: no # operators present.
| 05 |    |                           |  'foo' Stage 3: Concatenation elided: no ## operators present.
| 06 |    |                           |  'foo' Stage 4: Argument scan begins.
| 07 |    |                           |    Argument for parameter 'x' is "baz(p)"
| 08 | 02 |    baz(p)                 |    Scanning "baz(p)" for macros to expand.
| 09 |    |                           |    Definition found for 'baz': `baz(y)` = "qux ## y"
| 10 | 03 |    qux ## y               |    'baz' begins expansion. Original tokens shown.
| 11 | 04 |    qux ## p               |      'foo->baz' Stage 1: Raw parameter replacements performed
| 12 |    |                           |         using 'y' = "p".
| 13 |    |                           |      'foo->baz' Stage 2: Stringification elided: no # operators present.
| 14 | 05 |    quxp                   |      'foo->baz' Stage 3: Concatenation performed.
| 15 |    |                           |      'foo->baz' Stage 4: Argument scan elided: no parameters present.
| 16 |    |                           |      'foo->baz' Stage 5: Expansive parameter replacements elided: no parameters present.
| 17 |    |                           |      'foo->baz' Stage 6: Rescan begins
| 18 |    |                           |        No definition for 'quxp'
| 19 |    |                           |      'foo->baz' Stage 6: Rescan concludes.
| 20 | 06 |    quxp                   |    'baz' concludes expansion. Final result shown.
| 21 |    |                           |  'foo' Stage 4: Argument scan continues.
| 22 |    |                           |    Currently:
| 23 |    |                           |      'x' = "quxp"
| 24 |    |                           |      'y' = To Be Determined
| 25 |    |                           |    Argument for parameter 'y' is "q"
| 26 | 07 |    q                      |    Scanning "q" for macros to expand.
| 27 |    |                           |    Definition found for 'q': `q` = "r"
| 28 | 08 |    r                      |    'q' begins expansion. Original tokens shown.
| 29 |    |                           |      'foo->q': Stage 1: Concatenation elided: no ## operators present.
| 30 |    |                           |      'foo->q': Stage 2: Scan begins.
| 31 |    |                           |        No definition for 'r'
| 32 |    |                           |      'foo->q': Stage 2: Scan concludes.
| 33 | 09 |    r                      |    'q' concludes expansion. Final result shown.
| 34 |    |                           |  'foo' Stage 4: Argument scan concludes.
| 35 | 10 |  bar(x, baz(y))           |  'foo': Reminder of current token sequence.
| 36 | 11 |  bar(quxp, baz(r))        |  'foo' Stage 5: Expansive parameter replacements performed
| 37 |    |                           |     using 'x' = "quxp",
| 38 |    |                           |       and 'y' = "r".
| 39 |    |                           |  'foo' Stage 6: Rescan begins
| 40 |    |                           |    Definition found for 'bar': `bar(x,z)` = "x ## z"
| 41 | 12 |    x ## z                 |    'bar' begins expansion. Original tokens shown.
| 42 | 13 |    quxp ## baz(r)         |      'foo->bar' Stage 1: Raw parameter replacements performed
| 43 |    |                           |         using 'x' = "quxp",
| 44 |    |                           |           and 'z' = "baz(r)".
| 45 |    |                           |      'foo->bar' Stage 2: Stringification elided: no # operators present.
| 46 | 14 |    quxpbaz(r)             |      'foo->bar' Stage 3: Concatenation performed.
| 47 |    |                           |      'foo->bar' Stage 4: Argument scan elided: no parameters present.
| 48 |    |                           |      'foo->bar' Stage 5: Expansive parameter replacements elided: no parameters present.
| 49 |    |                           |      'foo->bar' Stage 6: Rescan begins
| 50 |    |                           |        No definition for 'quxpbaz'
| 51 |    |                           |        No definition for '('
| 52 |    |                           |        No definition for 'r'
| 53 |    |                           |        No definition for ')'
| 54 |    |                           |      'foo->baz' Stage 6: Rescan concludes.
| 55 | 15 |    quxpbaz(r)             |    'bar' concludes expansion. Final result shown.
| 56 |    |                           |  'foo' Stage 6: Rescan concludes
| 57 | 16 |  quxpbaz(r)               |  'foo' concludes expansion. Final result shown.
'----'----'---------------------------'-----------------------------------------

(旁注和对未来读者的警告:我手工编写了上述跟踪,它可能不是 100% 正确,至少在表示预处理器的工作方式方面。)

请注意,我不仅试图说明预处理器关于做什么的积极决定(例如:当它找到定义并开始扩展时),而且还说明了它关于做什么的消极决定>不要做(例如:当一个标记没有定义或#+## 运算符不存在时)。这听起来可能有点具体,但是对于理解为什么预处理器没有做我期望它做的事情很重要,通常会得出一个平凡的结论,比如“我拼错了定义或标记”或“我忘了#包括那个文件”。

如果有办法揭示 MSVC 的 CL.EXE 在使用“传统预处理器”逻辑扩展我的宏时的想法,我会更加放心。

这是一个没有回答问题的例子:

$ gcc -E somefile.h
...
quxpbaz(r)

这就是我在Any utility to test expand C/C++ #define macros? 等问题的答案中发现的。

当有人要求查看宏的“扩展”时,gcc -E 似乎是一个有效的答案。我正在寻找保真度更高的东西,我已经知道gcc -E

我正在编写 ISO C11 代码,但我包含 C++ 标签,以防该生态系统中存在与此相关的工具或技术。

我希望读到这篇文章的人可能是做过或看过类似工作的编译器编写者(编译器跟踪选项?),或者编写过这样的工具,或者他们的搜索结果比我幸运得多已经。或者,如果您密切关注所有 C 语言产品并且相对确定这不存在,那么我会发现一个否定的答案也很有帮助,尽管我很好奇为什么 C 预处理器可能已经存在了几十年,因其“陷阱”而臭名昭著,但仍然从未见过可以拉开预处理器帷幕的工具(或过程)。 (我希望这确实存在。手指交叉

【问题讨论】:

由于关于推荐工具的问题是题外话,我稍微修改了第一段:D 我在这里真的得到了“亲爱的圣诞老人......”的印象。但很清楚,经过研究,我自己非常想要它。因此,请投赞成票而不是赞成票。 ;-) 虽然这样的特性对预处理器的作者来说肯定是有用的,但我认为这对几乎所有用户来说都不是一件好事。如果您的预处理器魔法如此复杂以至于您需要查看此分析,您应该考虑重新考虑您的设计。 你不需要反汇编gcc来找到预处理器。这些可能更容易扩展以做你想做的事”github.com/boostorg/wavegithub.com/lpsantil/ucppgithub.com/facebookresearch/CParser 不确定这是否是您要找的,但 Eclipse 等 IDE 可以逐步扩展。当然不像你描述的那么冗长,但足够方便。 ***.com/questions/35472290/… 【参考方案1】:

我建议找一个质量好的编译器/预处理器并编辑预处理器。

我会避免使用 GCC 和 clang,因为它们对 IMO 来说太重了。我会看看 libfirm 中的 cparser,尤其是这个文件:https://github.com/libfirm/cparser/blob/master/src/parser/preprocessor.c

来自 libfirm 的代码非常易于阅读和编辑,构建项目几乎不需要时间 - 与 LLVM/clang 或 GCC 形成鲜明对比。

到目前为止,它已经吃掉了我扔给它的所有 C99 代码。

顺便说一句,我不隶属于,我只是觉得它很摇滚!我刚刚使用了代码并取得了很好的效果,并在 IRC 频道#firm @ freenode 上获得了出色的支持、帮助和指导。

编辑:

Linux 中的内核管理员团队使用的 Sparse 也很容易用于此类目的。它还包括一个 c 预处理器:https://github.com/chrisforbes/sparse

https://www.kernel.org/doc/html/v4.12/dev-tools/sparse.html

【讨论】:

以上是关于用于在宏扩展期间跟踪 C 预处理器执行的工具?的主要内容,如果未能解决你的问题,请参考以下文章

如何让 C 预处理器在编译期间执行代码?

使用 Boost/Wave C/C++ 预处理器的选择性宏扩展

读书笔记--预处理宏定义

C杂项一谈

Eclipse 问题 - 编译期间不考虑在 .c 和 .cpp 文件中定义的预处理

用于返回重复一定次数的字符串的 C 预处理器宏