通过静态分析在C项目中查找未使用的函数

时间:2011-06-17 14:18:55

标签: c gcc static-analysis dead-code

我正在尝试对C项目运行静态分析,以识别死代码,即从未调用的函数或代码行。我可以使用Visual Studio .Net for Windows或使用gcc for Linux构建此项目。我一直试图找到一些合理的工具,可以为我做到这一点,但到目前为止,我还没有成功。我已经阅读了有关Stack Overflow的相关问题,例如thisthis,我尝试将-Wunreachable-code与gcc一起使用,但gcc中的输出不是很有帮助。它具有以下格式

/home/adnan/my_socket.c: In function ‘my_sockNtoH32’: 
/home/adnan/my_socket.c:666: warning: will never be executed

但是当我查看my_socket.c中的第666行时,它实际上位于从函数my_sockNtoH32()调用的另一个函数中,并且不会针对此特定实例执行,而是在从其他函数调用时执行。

我需要的是找到永远不会被执行的代码。有人可以帮忙吗?

PS:我无法说服管理层为此任务购买工具,所以请坚持使用免费/开源工具。

3 个答案:

答案 0 :(得分:3)

如果海湾合作委员会没有为您裁减,请尝试clang(或更确切地说,static analyzer)。它(通常,你的里程可能会有所不同)具有比GCC更好的静态分析(并产生更好的输出)。它在Apple的Xcode中使用,但它是开源的,可以单独使用。

答案 1 :(得分:1)

当GCC说“永远不会被执行”时,就意味着它。实际上,您可能有一个错误,它确实会产生死代码。例如,像:

if (a = 42) {
    // some code
} else {
    // warning: unreachable code
}

当然,如果没有看到代码,就不可能具体。

请注意,如果第666行有一个宏,那么GCC也可能引用该宏的一部分。

答案 2 :(得分:0)

GCC将帮助您在编辑中找到死代码。如果它可以在多个编译单元中找到死代码,我会感到惊讶。编译单元中的函数或变量的文件级声明意味着某些其他编译单元可能引用它。所以在文件顶层声明的任何东西,GCC都无法消除,因为它可以说一次只能看到一个编译单元。

问题越来越严重。想象一下,编译单元A声明了函数a,编译单元B有一个调用a的函数b。死了吗?从表面上看,没有。但事实上,这取决于;如果b已经死了,并且对a的唯一引用是在b中,那么a也是死的。如果b仅使用& a 并将其放入数组X中,我们会遇到同样的问题。现在要确定a是否已死,我们需要在整个系统中进行分数分析,看看是否指向a的指针用于任何地方

要获得这种准确的“死”信息,您需要整个编译单元集的全局视图,并需要计算点到分析,然后根据这些点构建调用图 - 分析。函数a只在调用图(如树, 以main作为根)不会在某处引用它。 (有些警告是必要的:无论分析是什么,作为一个实际问题,它必须是保守的,所以即使是完整的分析也可能无法将函数正确识别为死亡。您还必须担心外部使用C伪影C函数的集合,例如,从一些汇编程序代码的调用)。

线程使情况变得更糟;每个线程都有一些根函数,它可能位于调用DAG的顶部。由于C编译器没有定义线程如何启动,因此应该清楚的是,为了确定多线程C应用程序是否具有死代码,必须以某种方式分析线程根函数,或者告诉如何通过以下方式发现它们:寻找线程初始化原语。

对于如何获得正确答案,您没有得到很多回复。虽然它不是开源的,但DMS Software Reengineering Toolkit及其C Front End具有执行此操作的所有机制,包括C解析器,control- and dataflow- analysis, local and global points-to analysis, and global call graph construction. DMS可轻松自定义以包含额外信息,如外部调用来自汇编程序,和/或作为线程初始化调用的线程根或特定源模式的列表,我们实际上(很容易)为具有数百万行代码的大型嵌入式引擎控制器做到了这一点。为了构建这样的调用图,DMS已被应用于大约2600万行代码(大约18,000个编译单元)的系统。

[有趣的是:在处理单个编辑单元时,DMS因缩放原因实际上删除了该编译单元中未使用的符号和相关代码。值得注意的是,当您考虑到通常隐藏在包含文件嵌套中的冰山时,这会消除大约95%的代码。它说C软件通常包含很差的包含文件。我怀疑你们都已经知道了。]

像GCC这样的工具会在编译时删除死代码。这很有帮助,但是死代码仍然存在于你的编译单元源代码中,引起了开发人员的注意(他们必须弄清楚它是否已经死了!)。可以配置程序转换模式下的DMS,模拟一些预处理器问题,从源中实际删除死代码。在非常大的软件系统上,你真的不想手动这样做。