我如何知道代码中的哪些部分从未使用过?

我有传统的C ++代码,我应该从中删除未使用的代码。 问题是代码库很大。

我怎样才能找出哪些代码是从来没有被调用/从未使用?

有两种不用的代码:

  • 本地的,也就是说,在一些函数中,一些path或variables没有被使用(或者被使用,但是没有意义的方式,如书面但是从不读)
  • 全球性的:永远不会调用的函数,永远不会被访问的全局对象

对于第一种,一个好的编译器可以帮助:

  • -Wunused (GCC, Clang )应该警告未使用的variables,Clang未使用的分析器甚至会增加警告有关从不读取的variables(即使使用)。
  • -Wunreachable-code ( 在2010年删除旧的GCC)应该警告有关本地块永远不会被访问(它发生在早期返回或条件总是评估为真)
  • 我没有select来警告未使用的catch块,因为编译器通常不能certificate不会抛出任何exception。

对于第二种,这是困难得多。 静态地,它需要整个程序分析,并且即使链接时间优化实际上可以移除死代码,实际上程序在执行时已经被转换了很多,几乎不可能向用户传递有意义的信息。

因此有两种方法:

  • 理论上是使用静态分析器。 一个软件,将一次检查整个代码的细节,并find所有的stream程path。 在实践中,我不知道任何在这里工作。
  • 实用的是使用启发式:使用代码覆盖工具(在GNU链中它是gcov 。注意在编译期间应该传递特定的标志以使其正常工作)。 你运行的代码覆盖工具有一个很好的不同的input(你的unit testing或非回归testing),死代码必须在未获得的代码…所以你可以从这里开始。

如果你对这个主题非常感兴趣,并且有时间和意愿去实际制定一个工具,我会build议使用Clang库来build立这样一个工具。

  1. 使用Clang库来获取AST(抽象语法树)
  2. 从入口点开始进行标记和扫描分析

因为Clang会为你parsing代码,并执行重载parsing,所以你不必处理C ++语言规则,你就可以专注于手头的问题。

然而,这种技术无法识别未被使用的虚拟覆盖,因为它们可能被第三方代码调用,这是你无法推测的。

对于未使用的整个函数(以及未使用的全局variables),只要您使用GCC和GNU ld,GCC实际上可以为您完成大部分工作。

编译源代码时,使用-ffunction-sections-fdata-sections ,然后在链接时使用-ffunction-sections-ffunction-sections-fdata-sections -Wl,--gc-sections,--print-gc-sections 。 现在链接器将列出所有可能被删除的函数,因为它们从来没有被调用,所有的全局variables都没有被引用。

(当然,你也可以跳过--print-gc-sections部分,让链接器静静地移除这些函数,但是把它们保存在源代码中。)

注意:这只会发现未使用的完整函数,它不会对函数内的死代码做任何事情。 在活动函数中从死代码中调用的函数也将被保留。

一些特定于C ++的function也会导致问题,特别是:

  • 虚拟function。 如果不知道哪个子类存在,哪些子类在运行时实际实例化,则无法知道最终程序中需要存在哪些虚函数。 链接器没有足够的信息,所以它必须保持所有的周围。
  • 具有构造函数的全局variables及其构造函数 一般来说,链接器不能知道全局的构造函数没有副作用,所以它必须运行它。 显然这意味着全球本身也需要保持。

在这两种情况下,虚拟函数或全局variables构造函数所使用的任何东西也都必须被保留。

另外需要注意的是,如果你正在构build一个共享库,GCC中的默认设置将会导出共享库中的每一个函数 ,从而就连接器而言,它将被“使用”。 要解决这个问题,需要将默认设置为隐藏符号而不是导出(使用例如-fvisibility=hidden ),然后明确地select需要导出的导出函数。

那么如果你使用g ++,你可以使用这个标志-Wunused

根据文件:

如果一个variables没有被使用,每当一个函数声明为静态但从未定义,每当一个标签被声明但没有被使用,以及每当一个语句计算出一个显式的不被使用的结果。

http://docs.freebsd.org/info/gcc/gcc.info.Warning_Options.html

编辑 :这里是其他有用的标志-Wunreachable-code文档:

当编译器检测到至less有一整行源代码将永远不会被执行时,这个选项是为了警告,因为某些条件永远不会被满足,或者因为它是在永远不会返回的过程之后。

更新 :我发现类似的主题旧版C / C ++项目中的死代码检测

我认为你正在寻找一个代码覆盖的工具。 一个代码覆盖工具将分析你的代码在运行时,它会让你知道哪一行代码被执行了多less次,以及哪些代码不被执行。

您可以尝试给这个开源代码覆盖工具一个机会: TestCocoon – C / C ++和C#的代码覆盖工具。

真正的答案是: 你永远不可能真的知道。

至less,对于非平凡的情况,你不能确定你已经得到了全部。 考虑维基百科有关无法访问的代码的文章以下内容:

 double x = sqrt(2); if (x > 5) { doStuff(); } 

正如维基百科正确指出的那样,一个聪明的编译器也许能够抓住这样的东西。 但考虑一个修改:

 int y; cin >> y; double x = sqrt((double)y); if (x != 0 && x < 1) { doStuff(); } 

编译器会抓住这个吗? 也许。 但要做到这一点,它将需要做更多的运行sqrt对恒定的标量值。 它将不得不弄清楚(double)y总是一个整数(容易),然后理解sqrt对于整数集(hard)的math范围。 一个非常复杂的编译器可能可以为sqrt函数或math.h中的每个函数执行此操作,也可以为任何可find其域的固定input函数执行此操作。 这变得非常非常复杂,复杂性基本上是无限的。 你可以不断地向编译器添加复杂层次,但总会有一种方法可以潜入某些代码,这些代码对于任何给定的input集合来说都是不可达的。

然后有input集, 从来没有进入。 在现实生活中没有任何意义的input,或被别处的validation逻辑阻止。 编译器无法知道这些信息。

最终的结果是,虽然其他人提到的软件工具是非常有用的,但是你永远不会知道你是否抓住了所有的东西,除非你手动完成代码。 即使这样,你也永远不会确定你没有错过任何东西。

恕我直言,唯一真正的解决scheme就是尽可能保持警惕,尽可能使用自动化,在可能的地方重构,并不断寻找改进代码的方法。 当然,无论如何,这是一个好主意。

我自己并没有使用它,但cppcheck ,声称find未使用的function。 这可能不会解决完整的问题,但可能是一个开始。

你可以尝试使用Gimple软件的PC-lint / FlexeLint 。 它声称

在整个项目中查找未使用的macros,typedef,类,成员,声明等

我用它进行静态分析,发现它非常好,但我不得不承认,我没有用它来专门find死代码。

我寻找未使用的东西的正常方法是

  1. 确保构build系统正确处理依赖关系跟踪
  2. 设置第二个显示器,全屏terminal窗口,运行重复构build,并显示第一个屏幕的输出。 watch "make 2>&1"在Unix上往往会诡计多端。
  3. 在整个源代码树上运行查找和replace操作,在每行的开头添加“//?”
  4. 修复由编译器标记的第一个错误,通过删除“//?” 在相应的行中。
  5. 重复,直到没有错误。

这是一个有点漫长的过程,但它确实给出了很好的结果。

标记尽可能多的公共函数和variables作为私有或保护而不会导致编译错误,同时这样做,也尝试重构代码。 通过将函数设置为私有的并在一定程度上受到保护,由于私有函数只能从同一个类中调用,因此缩小了search范围(除非有一些愚蠢的macros或其他技巧来规避访问限制,如果是这种情况,我build议您找一份新工作)。 确定您不需要私有函数会容易得多,因为只有您当前正在处理的类可以调用此函数。 如果您的代码库有小类,并且松散耦合,则此方法更容易。 如果你的代码库没有小类,或者耦合非常紧密,我build议先清理它们。

接下来将标记所有剩下的公共职能,并制作一个调用图表来找出这些类之间的关系。 从这棵树中,试图找出分支的哪一部分看起来可以被修剪。

这个方法的优点是你可以在每个模块的基础上做,所以当你有很多的代码库时,很容易在没有很长时间的情况下通过你的unit testing。

如果你在Linux上,你可能想看看callgrind ,一个C / C ++程序分析工具,它是valgrind套件的一部分,它也包含检查内存泄漏和其他内存错误的工具(你也应该使用它)。 它分析一个正在运行的程序实例,并生成有关其调用图的数据以及有关调用图上节点的性能成本。 它通常用于性能分析,但它也会为您的应用程序生成一个调用图表,以便您可以看到调用哪些函数以及调用者。

这显然是页面上其他地方提到的静态方法的补充,它只会有助于消除完全不用的类,方法和函数 – 它不会帮助在实际调用的方法中find死代码。

我真的没有使用任何工具来做这样的事情……但是,就我所见到的所有答案而言,从来没有人说过这个问题是不可信的。

这是什么意思? 这个问题不能通过计算机上的任何algorithm来解决。 这个定理(这种algorithm不存在)是图灵停机问题的必然结果。

您将使用的所有工具不是algorithm,而是启发式(即不是精确的algorithm)。 他们不会给你所有没有使用的代码。

一种方法是使用debugging器和编译期间消除未使用的机器代码的编译器function。

一旦一些机器代码被淘汰,debugging器就不会让你在相应的源代码行中放置一个breakpojnt。 所以你把断点放在任何地方,启动程序并检查断点 – 那些在“没有为这个源码加载代码”的状态对应于被删除的代码 – 无论代码是从不调用还是已经内联,你必须执行一些最小分析找出其中哪些发生了。

至less这是如何在Visual Studio中工作,我猜其他工具集也可以做到这一点。

这是很多工作,但我想比手动分析所有代码更快。

这取决于您用来创build应用程序的平台。

例如,如果您使用Visual Studio,则可以使用.NET ANTS Profiler这样的工具,它能够parsing和分析代码。 这样,你应该很快知道你的代码的哪一部分被实际使用了。 Eclipse也有相同的插件。

否则,如果您需要知道最终用户实际使用的应用程序的function,并且您可以轻松地释放应用程序,则可以使用日志文件进行审计。

对于每个主要function,您可以跟踪其使用情况,并在几天/每周后获取该日志文件,并查看它。

CppDepend是一个商业工具,可以检测未使用的types,方法和领域,并做更多。 它可用于Windows和Linux(但目前没有64位支持),并且有2周的试用版。

免责声明:我没有在那里工作,但我拥有这个工具的许可证(以及NDepend ,这是一个更强大的替代.NET代码)。

对于那些好奇的人来说,下面是用CQLinq编写的内置(可定制)规则来检测死亡方法:

 // <Name>Potentially dead Methods</Name> warnif count > 0 // Filter procedure for methods that should'nt be considered as dead let canMethodBeConsideredAsDeadProc = new Func<IMethod, bool>( m => !m.IsPublic && // Public methods might be used by client applications of your Projects. !m.IsEntryPoint && // Main() method is not used by-design. !m.IsClassConstructor && !m.IsVirtual && // Only check for non virtual method that are not seen as used in IL. !(m.IsConstructor && // Don't take account of protected ctor that might be call by a derived ctors. m.IsProtected) && !m.IsGeneratedByCompiler ) // Get methods unused let methodsUnused = from m in JustMyCode.Methods where m.NbMethodsCallingMe == 0 && canMethodBeConsideredAsDeadProc(m) select m // Dead methods = methods used only by unused methods (recursive) let deadMethodsMetric = methodsUnused.FillIterative( methods => // Unique loop, just to let a chance to build the hashset. from o in new[] { new object() } // Use a hashet to make Intersect calls much faster! let hashset = methods.ToHashSet() from m in codeBase.Application.Methods.UsedByAny(methods).Except(methods) where canMethodBeConsideredAsDeadProc(m) && // Select methods called only by methods already considered as dead hashset.Intersect(m.MethodsCallingMe).Count() == m.NbMethodsCallingMe select m) from m in JustMyCode.Methods.Intersect(deadMethodsMetric.DefinitionDomain) select new { m, m.MethodsCallingMe, depth = deadMethodsMetric[m] } 

我不认为这可以自动完成。

即使使用代码覆盖工具,也需要提供足够的input数据才能运行。

可能是非常复杂和昂贵的静态分析工具,如来自Coverity或LLVM编译器的帮助。

但我不确定,我更喜欢手动代码审查。

更新

那么..只删除未使用的variables,但未使用的function并不难。

更新

在阅读其他答案和评论之后,我更坚信这是不能做到的。

您必须知道代码才能获得有意义的代码覆盖率测量,而且如果您知道大量的手动编辑将比准备/运行/审查覆盖率结果更快。

今天我有个朋友问我这个问题,而且我环顾了一些有希望的Clang开发,例如ASTMatcher和静态分析器 ,在编译过程中可能具有足够的可见性以确定死代码段,但是随后我发现这个:

https://blog.flameeyes.eu/2008/01/today-how-to-identify-unused-exported-functions-and-variables

这几乎是一个完整的描述,如何使用几个GCC标志看起来是为了识别未引用的符号!

如果某个函数被调用的一般问题是NP-Complete。 如果一个函数被调用,你不能预先知道一个图灵机是否会停止。 你可以得到,如果有一些path(静态),从main()到你写的函数,但是不能保证它永远不会被调用。

那么如果你使用g ++,你可以使用这个标志–Wunused

根据文件:

 Warn whenever a variable is unused aside from its declaration, whenever a function is declared static but never defined, whenever a label is declared but not used, and whenever a statement computes a result that is explicitly not used. 

http://docs.freebsd.org/info/gcc/gcc.info.Warning_Options.html

编辑:这里是其他有用的标志 – 无法访问的代码相关文件:

 This option is intended to warn when the compiler detects that at least a whole line of source code will never be executed, because some condition is never satisfied or because it is after a procedure that never returns.