追踪C预处理器执行宏展开过程的工具?

18

有没有一种方法可以逐步打印出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%正确,至少在代表预处理器工作方式方面)

请注意,我试图不仅说明预处理器在决定要做什么方面的积极决策(例如:当它发现一个定义并开始展开时),还说明了它在决定不要做什么方面的消极决策(例如:当标记没有定义或不存在# + ##运算符时)。这听起来可能有点具体,但理解为什么预处理器没有执行我期望它执行的操作非常重要,通常得出的结论是:“我拼错了定义或标记”或“我忘记#include那个文件”等平凡结论。

如果有一种方法可以揭示MSVC的CL.EXE在使用“传统预处理器”逻辑扩展我的宏时在思考什么,我会更加欣慰。

以下是一个不回答问题的示例:

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

我发现在像有没有测试扩展C/C++ #define宏的实用工具?这样的问题的回答中,大多数人认为gcc -E是一个有效的答案,但我正在寻找一种更高保真度的工具。我已经了解了gcc -E

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

我希望看到这篇文章的人可能是编译器编写者,曾经做过或看过类似的工作(编译器跟踪选项?),或者已经编写过这样的工具,或者只是比我搜索结果更好运。如果你了解所有C语言的提供者,并且相对确定这种工具不存在,那么负面答案对我也很有帮助,虽然我会好奇为什么C预处理器已经存在几十年,因其“陷阱”而臭名昭着,却从未看到过拉开预处理器幕布的工具(或过程)。 (我希望这个工具实际存在。 祈祷


3
由于询问关于推荐工具的问题属于禁止主题,我稍微修改了第一段 :D - Antti Haapala -- Слава Україні
1
我真的有一种“亲爱的圣诞老人……”的感觉。但它很清晰,经过研究,我非常想要它。所以,我会给你点赞而不是关闭投票。;-) - Yunnosch
1
虽然这样的功能对于预处理器的作者肯定很有用,但我认为对于几乎所有用户来说并不是一件好事。如果你的预处理器魔法如此复杂以至于需要看到这个分析结果,那么你应该考虑重新思考你的设计。 - the busybee
1
你不需要拆解gcc来查找预处理器。这些可能更容易扩展以实现你想要的功能:https://github.com/boostorg/wave,https://github.com/lpsantil/ucpp,https://github.com/facebookresearch/CParser。 - Jerry Jeremiah
1
不确定这是否是您要寻找的内容,但是像Eclipse这样的IDE可以进行逐步展开。肯定不像您描述的那么详细,但足够方便。https://dev59.com/yVsW5IYBdhLWcg3wCTeB - Eugene Sh.
显示剩余5条评论
1个回答

3
我建议找一个质量好的编译器/预处理器并编辑预处理器。 我认为GCC和clang太过沉重,我会看一下libfirm的cparser以及特定的文件:https://github.com/libfirm/cparser/blob/master/src/parser/preprocessor.c。 来自libfirm的代码非常易于阅读和编辑,并且构建项目所需的时间几乎可以忽略不计 - 与LLVM/clang或GCC形成了明显的对比。 到目前为止,它已经吃掉了我抛给它的所有C99代码。 顺便说一句,我没有任何关联,我只是认为它很棒!我只是用这个代码取得了很好的结果,并在IRC频道#firm @ freenode上获得了极好的支持、帮助和指导。 编辑: Sparse,由Linux内核管理团队使用,也很容易进行此类hack。它还包括一个C预处理器:https://github.com/chrisforbes/sparse

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接