【问题标题】:How do I strip out inactive #if directives with the gcc/g++ preprocessor?如何使用 gcc/g++ 预处理器去除不活动的 #if 指令?
【发布时间】:2014-09-25 04:47:40
【问题描述】:

我正在使用第三方开源项目,需要去掉不活动的#ifs、#ifdefs 等,以便更好地理解代码流。

有没有办法在没有这些指令的情况下使用 make 来生成源文件的版本?我想避免扩展宏,只删除指令。

我在看 https://gcc.gnu.org/onlinedocs/gcc/Preprocessor-Options.html

似乎 -dD 和 -fdirectives-only 是不错的选择。

这些预处理文件会出现在哪里?我在哪里添加这些命令以与 Makefile 和“make”一起使用? 我尝试运行“make -n”来生成一个脚本,并在 -Wformat 之后向脚本中的 g++ 和 gcc 调用添加选项,但我没有注意到任何事情。

我不确定这是否会使任何事情复杂化,但我也在使用 avr-gcc 和 avr-g++。

我查看了 coan,它不支持 #included #defines,因此它不能用于此目的,而且我无法让 sunifdef 工作。有没有办法用预处理器做到这一点。

定义分散在当前文件、包含文件和包含指定 -Dfoo=opt 选项的 makefile 中。

【问题讨论】:

  • 您可以 fork 一个库并获得一个开源项目的“仅适用于我的机器”版本。但是不要指望任何人在你完成后支持或贡献它,他们把那些#ifdefs 放入代码中汗流浃背。有点像青少年性行为,一个错误,你将不得不在你的余生中支持它:)
  • 是的,但是当流程被无数指令所掩盖时,可能很难理解发生了什么。
  • 不知道sunifdef 是什么,可能是unifdef 的一些分支。为什么它对你没有帮助?
  • 是什么使 sunifdef 和 unifdef 不适合您?这正是他们的目的。
  • “我无法让 sunifdef 工作”——所以您已经有了问题的答案。正确的问题应该旨在让它发挥作用。

标签: c++ c gcc


【解决方案1】:

您的预处理器选项走在了正确的轨道上。 -D 将定义一个值为 1 的宏,-U 将取消任何先前的定义(它将变为未定义),而 -fdirectives-only 将抑制宏扩展。除此之外,您还可以在 gcc 中使用 -E 标志来告诉它提供预处理器输出作为单独的文件供您检查。但是,我认为它们不会像您所期望的那样。正如this SO question 所建议的,CPP(C 预处理器)输出可能添加了其他内容,您应该检查 gnu CPP output manual page。这就是您将从 CPP 获得的。

听起来您希望能够一次性剥离这些无关代码并从那里进行开发。为此,我鼓励您再试一次 unifdef。这就是 unifdef 的设计目的,而 CPP 的设计目的是为编译准备代码。它们是不同的任务,因此您应该为它们使用正确的工具。它在http://dotat.at/prog/unifdef/ 上作为独立应用程序提供,并内置在一些Linux Shells 中。

它允许您指定您希望它认为已定义或未定义的宏,并且它会删除条件指令将评估为假的代码块。例如,您可以这样运行它:

unifdef -I -DMACRO1 -UMACRO2

它会通过C/C++源文件搜索指定的目录,寻找#if#ifdef#ifndef等,遇到时会评估条件表达式并有选择地删除由该表达式控制的代码。考虑使用以下代码的输入文件:

int i = 0;
#ifdef MACRO1
int j = 0;
#endif /* ifdef MACRO1 */
int k = 0;

int m = 0;
#if (MACRO1 && MACRO2)
int n = 0;
#endif /* if (MACRO1 && MACRO2) */
int p = 0;

int q = 0;
#ifdef MACRO3
int r = 0;
#endif /* ifdef MACRO3 */
int t = 0;

如果我们像上面的例子一样调用 unifdef,输出将是这样的:

int i = 0;
int j = 0;
int k = 0;

int m = 0;
int p = 0;

int q = 0;
#ifdef MACRO3
int r = 0;
#endif /* ifdef MACRO3 */
int t = 0;

请注意,n 的声明已被删除,因为它包含在预处理器 #if/#endif 块中,其控制表达式评估为 false(我们告诉 unifdef 认为 MACRO2 未定义)。 j 的声明仍然存在,但 #ifdef#endif 语句被删除,因为已知控制表达式为真。

依赖于 MACRO3 的块保持不变,因为它的状态是未知的。

对于它的运行方式也有很大的灵活性和控制力。

如果您决定确实希望它成为构建过程的一部分,您可以随时将其添加到您的 makefile 中。

如果您没有应定义或未定义的可用宏的列表,您可以使用 unifdef 提供的“unifdefall”脚本,它将使用 CPP 自行发现源代码中的宏定义,并且根据源代码中包含的定义删除/保留代码块。

TL;DR

是的,你可以(有点)用预处理器来做。但是 unifdef 和 sunifdef 是专门为此而设计的工具,因此您应该改用它们。

【讨论】:

  • 感谢您抽出宝贵时间为 Scott 输入此内容!我担心 unifdef 可能会产生与 CPP 不一致的东西。 unifdef 是否会处理依赖图(如果某些东西得到#def'd,然后是#undef'd,那么依赖图遍历的顺序是相关的)。 unifdef 会处理您正在编译的机器附带的隐式定义吗?此外,Makefile 包含许多其他 makefile,每个都指定 -D 选项,因此我必须遍历每个依赖的 makefile 并手动创建 -D 列表,这有点冒险。
  • 我不确定先定义然后取消定义(反之亦然)的顺序。您必须检查文档。这不是我以前必须担心的事情。至于 makefile -D 选项,它有一些风险,但您可以通过您的 makefile 查找 -D,将输出通过管道传输到文本文件,并将该文件用作 unifdef 输入。它可以从文本文件中读取定义并使用与 GCC 相同的 -D / -U 参数格式,并且如果您保存文件,则只需执行一次。像这样的自动化方法应该可以将搞砸的危险降到最低。
  • 如果这么简单就好了,但是 makefile 使用 makefile $VARIABLES,它是根据 make 文件中的条件分配的。然后在单独的步骤中将特定变量与前面的 -Ds 连接起来。有没有办法使用预处理器获取所有定义的列表,然后将其作为输入运行 unifdef,或者此时尝试清理预处理器输出以在没有定义的情况下取回原始文件更容易?
  • 您可以使用标志like this 将#define 列表从预处理器中取出,但您是正确的,这会导致收益递减,您不得不怀疑是否只是清理预处理器输出文件会更容易。老实说,我不知道,因为我不必处理您目前拥有的 makefile 混乱。我建议转储预处理器输出文件并粗略估计接收器清理它们的时间,并将其与您知道的 unifdef 路由所需的工作量进行比较。
  • @user221237 这也超出了我的认知。我不是一个经验丰富的 makefile 用户,之前也不必调用仅预处理器选项,所以我不确定如何去做。如果您无法自行使用它,我建议您打开一个新的 SO 问题,询问 具体 是否让 -dM 标志通过 makefile 工作。不过,一旦你有了这个设置,我认为让 unifdef 为你工作将是一件小事。
【解决方案2】:

假设

  1. 练习的目的是生成 C/C++ 源代码主体,其中大部分条件编译被删除,并编译成相同的二进制文件。
  2. 这是第三方源代码,后续更新合并的问题大家都知道。
  3. 这是开源的,但您无意分发修改后的源代码。
  4. 程序非常复杂,由任意复杂的 makefile 或类似工具构建,带有命令行符号定义和/或配置包含文件。

我的策略是使用像 unifdef 这样的程序。我第一次这样做是我自己编写的,您可能需要修改程序才能产生所需的结果。

核心策略是:

  1. 识别单个可能定义的符号(需要进行实验或反复试验)。
  2. 通过 unifdef 运行代码。
  3. (可选)直观地比较源之前和之后以发现明显的问题。
  4. 构建后续版本以确保其正确构建。
  5. 编译之前和之后的版本,以使用相同的 makefile 生成预处理输出。
  6. 比较预处理前后的源代码对。它们应该是相同的,给予或占据一些空白。
  7. 根据需要通过在版本之前或之后进行编辑来解决问题。
  8. (可选)从所有 makefile 中删除对该符号的所有引用。 [应该没什么区别。]
  9. 重复,使用后面的版本和不同的符号。

一次一个符号,每次都进行彻底的测试。有些符号可能会变得太难,如果你有超过一百万行的源代码和一百左右的符号,它可能会失控。

最后一步:如果您修改了 unifdef,请随时将您的更改回馈给社区。要做好这是一项非常具有挑战性的任务!

【讨论】:

  • 我编写了一个完成 95% 的脚本,而您手动修复了最后 5%。第一步是使用 make -n > mk.sh,将 -E -dM 添加到 mk.sh 中的底层 g++ 调用中,以获取用于链接对象的定义列表。将这些定义放在每个文件的顶部,在文件上运行 unifdef,然后删除那些 cat'd 定义。它不适用于包含保护或类似的包含(#ifndef a #define a),所以我用 seds/greps 编写了一些脚本来修复这个极端情况。这是一个 6 小时的磨难,但 40,000 行更改编译/运行
  • 如果您完成一天的工作就可以了。希望这能帮助其他人解决同样的问题。
【解决方案3】:

使用 make -n 创建由 makefile 生成的 shell 脚本。 转到运行 avr-g++ 的行并在所有其余选项之前添加 -dM -E。 转到 -o 之后的文件,#defines 列表将在那里(它可能应该是 something.o) 使用 unifdef -f definedFile.o 文件名

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-04-26
    • 2012-06-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-02
    相关资源
    最近更新 更多