GCC 将帮助您在编译中找到死代码。如果它可以跨多个编译单元找到死代码,我会感到惊讶。编译单元中函数或变量的文件级声明意味着其他编译单元可能会引用它。因此,在文件的顶层声明的任何内容,GCC 都无法消除,因为它可以说一次只能看到一个编译单元。
问题变得越来越难。假设编译单元 A 声明了函数 a,而编译单元 B 有一个函数 b 调用 a。是死人吗?从表面上看,没有。但实际上,这取决于;如果 b 已死,并且对 a 的唯一引用在 b 中,则 a 也已死。如果 b 只接受&a并将其放入数组 X 中,我们会遇到同样的问题。现在要确定 a 是否已死,我们需要对整个系统进行指向分析,以查看指向 a 的指针是否在任何地方使用。
要获得这种准确的“死”信息,您需要对整个编译单元集有一个全局视图,并且需要计算一个指向分析,然后基于该指向分析构建一个调用图. 仅当调用图(作为树,以 main 作为根)没有在某处引用它时,函数 a 才死。(一些警告是必要的:无论分析是什么,实际上它必须是保守的,因此即使是完整的分析点也可能无法正确地将函数识别为死函数。您还必须担心从外部使用 C 工件C 函数集,例如,从一些汇编代码调用 a)。
线程使情况变得更糟;每个线程都有一些根函数,它可能位于调用 DAG 的顶部。由于 C 编译器没有定义线程的启动方式,因此应该清楚地确定多线程 C 应用程序是否有死代码,必须以某种方式告诉线程根函数的分析,或者告诉如何通过寻找线程初始化原语。
关于如何获得正确答案,您没有得到很多回应。虽然它不是开源的,但我们的DMS 软件重组工具包及其C 前端具有执行此操作的所有机制,包括 C 解析器、控制和数据流分析、本地和全局点分析以及全局调用图构建. DMS 很容易定制以包含额外的信息,例如来自汇编程序的外部调用,和/或线程根列表或作为线程初始化调用的特定源模式,我们实际上已经(很容易)为一些大型嵌入式引擎控制器做到了这一点拥有数百万行代码。DMS 已被应用于多达 2600 万行代码(大约 18000 个编译单元)的系统,用于构建此类调用图。
[有趣的是:在处理单个编译单元时,出于缩放原因,DMS 实际上会删除该编译单元中未使用的符号和相关代码。值得注意的是,当您考虑到通常隐藏在包含文件嵌套中的冰山时,这消除了大约 95% 的代码量。它说 C 软件通常具有不良因素的包含文件。我想你们都已经知道了。]
像 GCC 这样的工具会在编译时删除死代码。这很有帮助,但死代码仍然存在于编译单元源代码中,占用了开发人员的注意力(他们也必须弄清楚它是否死了!)。可以配置 DMS 在其程序转换模式下,以一些预处理器问题为模,从源代码中实际删除该死代码。在非常大的软件系统上,您并不想手动执行此操作。