我有旧的C ++代码,应该从中删除未使用的代码。问题是代码库很大。
如何找出从未调用/从未使用过的代码?
f()
,并调用f()
明确地解析为1,那么它是不可能仅仅通过添加一个名为第3功能,使该呼叫决心2号f()
- “最坏的,你可以做通过添加第3个函数将导致调用变得模棱两可,从而阻止程序编译。很想(=感到恐惧)看到一个反例。
我有旧的C ++代码,应该从中删除未使用的代码。问题是代码库很大。
如何找出从未调用/从未使用过的代码?
f()
,并调用f()
明确地解析为1,那么它是不可能仅仅通过添加一个名为第3功能,使该呼叫决心2号f()
- “最坏的,你可以做通过添加第3个函数将导致调用变得模棱两可,从而阻止程序编译。很想(=感到恐惧)看到一个反例。
Answers:
有两种未使用的代码:
对于第一种,好的编译器可以帮助您:
-Wunused
(GCC,Clang)应该警告未使用的变量,Clang未使用的分析器甚至已经增加了警告从未读取(即使已使用)的变量。-Wunreachable-code
(较旧的GCC,于2010年删除)应警告从未访问的本地块(这种情况发生在早期返回或条件始终为true的情况下)catch
块的选项,因为编译器通常无法证明不会引发任何异常。对于第二种,这要困难得多。从静态上讲,它需要整个程序的分析,并且即使链接时间优化实际上可以删除无效代码,但实际上在执行程序时程序已进行了很大的转换,几乎无法向用户传达有意义的信息。
因此,有两种方法:
gcov
。请注意,在编译过程中应传递特定的标志,以使其正常工作)。您可以使用一组不错的输入(您的单元测试或非回归测试)来运行代码覆盖率工具,无效代码必然在未到达的代码之内……因此您可以从这里开始。如果您对该主题非常感兴趣,并且有时间和意愿自己开发一个工具,我建议您使用Clang库来构建这样的工具。
因为Clang会为您解析代码并执行重载解析,所以您不必处理C ++语言规则,并且可以专注于手头的问题。
但是,这种技术无法识别未使用的虚拟替代,因为它们可能被您无法推理的第三方代码调用。
foo()
在仅出现时被标记为“被呼唤” if (0) { foo(); }
将是一个奖励,但需要额外的
对于未使用的完整函数(和未使用的全局变量),只要您使用的是GCC和GNU ld,GCC实际上可以为您完成大部分工作。
编译源代码时,请使用-ffunction-sections
和-fdata-sections
,然后在链接使用时-Wl,--gc-sections,--print-gc-sections
。链接器现在将列出所有可能被删除的函数,因为它们从未被调用过,而所有全局变量从未被引用过。
(当然,您也可以跳过该--print-gc-sections
部分,让链接程序以静默方式删除功能,但将其保留在源代码中。)
注意:这只会找到未使用的完整函数,不会对函数中的无效代码进行任何处理。从活动函数中的死代码调用的函数也将保留。
一些特定于C ++的功能也会引起问题,尤其是:
在这两种情况下,虚函数或全局变量构造函数使用的所有内容都必须保留。
另一个警告是,如果您要构建共享库,则GCC中的默认设置将导出共享库中的每个函数,从而就链接器而言将其“使用”。要解决此问题,您需要将默认设置为隐藏符号而不是导出(使用例如-fvisibility=hidden
),然后显式选择您需要导出的导出函数。
好吧,如果您使用g ++,则可以使用此标志 -Wunused
根据文档:
当变量除了声明外没有被使用,函数被声明为静态但从未定义,标签被声明但未使用,语句计算的结果未明确使用时,均会发出警告。
http://docs.freebsd.org/info/gcc/gcc.info.Warning_Options.html
编辑:这是其他有用的标志-Wunreachable-code
根据文档:
此选项旨在在编译器检测到至少一整行源代码永远不会执行,因为某些条件从未满足或因为它在过程永不返回之后而发出警告时发出警告。
更新:我在旧的C / C ++项目中发现了类似的主题死代码检测
-Wunused
警告已声明(或一口气声明和定义)但实际上从未使用过的变量。顺便说一句,范围保护器非常令人讨厌:p Clang中有一个实验性的实现,可以警告已写入但从未读取过的非易失性变量(由Ted Kremenek编写)。-Wunreachable-code
警告某个函数中的代码无法到达,例如,它可以是位于throw
或return
语句之后的代码,或者是从未使用过的分支中的代码(例如在重言式比较的情况下发生)。
我认为您正在寻找代码覆盖率工具。一个代码覆盖率工具将在您的代码运行时对其进行分析,并让您知道执行了哪些代码行,执行了多少次,以及哪些没有执行。
您可以尝试给这个开源代码覆盖工具一个机会:TestCocoon -C / C ++和C#的代码覆盖工具。
void func()
在a.cpp中具有功能,该功能 在b.cpp中使用。编译器如何检查func()是否在程序中使用?这是链接器的工作。
真正的答案是:您永远不可能真正知道。
至少,对于非平凡的案例,您不能确定是否已全部掌握。考虑一下Wikipedia关于不可达代码的文章中的以下内容:
double x = sqrt(2);
if (x > 5)
{
doStuff();
}
正如Wikipedia正确指出的那样,一个聪明的编译器也许可以捕获类似的东西。但考虑修改:
int y;
cin >> y;
double x = sqrt((double)y);
if (x != 0 && x < 1)
{
doStuff();
}
编译器会抓住这个吗?也许。但是要做到这一点,除了在sqrt
恒定的标量值上运行之外,还需要做更多的事情。必须弄清楚它(double)y
始终是整数(简单),然后了解sqrt
整数集的数学范围(硬)。一个非常复杂的编译器可能可以针对该sqrt
函数,math.h中的每个函数,或针对其可以确定其范围的任何固定输入函数执行此操作。这变得非常非常复杂,并且复杂性基本上是无限的。您可以继续在编译器中添加复杂性,但是总会有一种方法可以潜入某些对于任何给定输入集都无法访问的代码。
还有一些输入集根本就不会输入。在现实生活中毫无意义的输入,或被其他地方的验证逻辑阻止的输入。编译器无法了解这些内容。
这样做的最终结果是,尽管其他人提到的软件工具非常有用,但是除非您事后手动检查代码,否则您将永远无法确定自己是否捕获了所有内容。即使那样,您也永远不会确定自己没有错过任何东西。
唯一真正的解决方案,恕我直言,要尽可能保持警惕,使用您可以使用的自动化,在可能的地方进行重构,并不断寻找改进代码的方法。当然,无论如何还是一个好主意。
我自己没有使用过它,但是cppcheck声称可以找到未使用的功能。它可能无法解决完整的问题,但这可能是一个开始。
cppcheck --enable=unusedFunction --language=c++ .
,可以找到这些未使用的功能。
您可以尝试使用Gimple Software提供的PC-lint / FlexeLint。它声称
在整个项目中查找未使用的宏,typedef,类,成员,声明等
我已经将它用于静态分析,并且发现它非常好,但是我不得不承认我没有使用它专门查找无效代码。
我发现未使用的东西的正常方法是
watch "make 2>&1"
倾向于在Unix上达到目的。这是一个冗长的过程,但确实能带来良好的效果。
在不引起编译错误的情况下,将尽可能多的公共函数和变量标记为私有或受保护,同时尝试重构代码。通过将函数设为私有并在某种程度上受保护,您可以缩小搜索范围,因为只能从同一类调用私有函数(除非有愚蠢的宏或其他技巧来规避访问限制,如果是这种情况,我建议您找新工作)。确定是否不需要私有函数要容易得多,因为只有当前正在处理的类才能调用此函数。如果您的代码库具有较小的类并且是松散耦合的,则此方法会更容易。如果您的代码库没有小类或耦合紧密,建议您先清理它们。
接下来将标记所有剩余的公共功能,并创建一个调用图以了解类之间的关系。从这棵树中,尝试找出分支的哪一部分看起来可以修剪。
这种方法的优点是您可以按模块进行操作,因此很容易保持通过单元测试的状态,而不会因代码库损坏而花费大量时间。
如果您使用的是Linux,则可能需要研究套件中callgrind
的C / C ++程序分析工具valgrind
,该工具还包含检查内存泄漏和其他内存错误(也应使用)的工具。它分析程序的正在运行的实例,并生成有关其调用图以及有关调用图上节点的性能成本的数据。它通常用于性能分析,但也会为您的应用程序生成调用图,因此您可以查看调用了哪些函数以及它们的调用方。
显然,这是对页面其他地方提到的静态方法的补充,并且仅对消除完全未使用的类,方法和函数有帮助-它完全无助于在实际调用的方法中查找无效代码。
我确实没有使用过任何能做到这一点的工具...但是,据我在所有答案中所看到的,没有人说过这个问题是无可争议的。
我是什么意思 无法通过计算机上的任何算法解决此问题。这个定理(不存在这样的算法)是图灵停止问题的推论。
您将使用的所有工具都不是算法,而是试探法(即,不是精确算法)。他们不会为您提供未使用的所有代码。
一种方法是使用调试器和编译器功能,以消除编译期间未使用的机器代码。
一旦消除了一些机器代码,调试器将不允许您在相应的源代码行上放一个断点。因此,您到处都放置断点,然后启动程序并检查断点-处于“未为此源加载任何代码”状态的断点对应于已删除的代码-要么从不调用该代码,要么已内联该代码,并且您必须执行一些最小操作分析以查找这两个事件中的哪一个发生了。
至少这是它在Visual Studio中的工作方式,我想其他工具集也可以做到这一点。
这项工作很多,但我想比手动分析所有代码要快。
CppDepend是一种商业工具,可以检测未使用的类型,方法和字段,并执行更多操作。它适用于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] }
这取决于您用来创建应用程序的平台。
例如,如果使用Visual Studio,则可以使用诸如.NET ANTS Profiler之类的工具,该工具能够解析和分析代码。这样,您应该快速知道实际使用了代码的哪一部分。Eclipse也有等效的插件。
否则,如果您需要了解最终用户实际使用的应用程序功能,并且可以轻松发布应用程序,则可以使用日志文件进行审核。
对于每个主要功能,您都可以跟踪其用法,几天/一周后,只需获取该日志文件,然后对其进行查看。
我认为这不会自动完成。
即使使用代码覆盖工具,您也需要提供足够的输入数据才能运行。
可能是非常复杂且价格昂贵的静态分析工具,例如Coverity或LLVM编译器提供的帮助。
但是我不确定,我希望手动检查代码。
更新
好吧..仅删除未使用的变量,但未使用的功能并不难。
更新
在阅读了其他答案和评论后,我更加坚信这是不可能完成的。
您必须知道代码才能具有有意义的代码覆盖率度量,并且如果您知道很多手动编辑将比准备/运行/查看覆盖率结果更快。
今天我有一个朋友问我这个问题,我环顾了一些有前途的Clang开发,例如ASTMatcher和Static Analyzer,它们可能在编译期间确定死代码部分时具有足够的可视性,但后来我发现了:
https://blog.flameeyes.eu/2008/01/today-how-to-identify-unused-exported-functions-and-variables
几乎完整地描述了如何使用一些GCC标志,这些标志似乎是为了识别未引用符号而设计的!
好吧,如果您使用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
编辑:这是其他有用的标志-Wunreachable-code根据文档:
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.