我有遗留的c++代码,我应该删除不使用的代码。问题是代码库很大。
我如何才能知道哪些代码从未被调用/从未被使用?
我有遗留的c++代码,我应该删除不使用的代码。问题是代码库很大。
我如何才能知道哪些代码从未被调用/从未被使用?
当前回答
我通常找没用的东西的方法是
确保构建系统正确地处理依赖项跟踪 设置第二个监视器,使用全屏终端窗口,运行重复构建并显示第一个满屏的输出。watch "make 2>&1"倾向于在Unix上做到这一点。 在整个源代码树上运行查找和替换操作,添加“//?”“在每一行的开头 通过删除相应行中的"//?"来修复编译器标记的第一个错误。 重复操作,直到没有错误。
这是一个有点漫长的过程,但确实能得到很好的结果。
其他回答
对于未使用的整个函数(和未使用的全局变量),GCC实际上可以为您完成大部分工作,前提是您使用GCC和GNU ld。
编译源代码时,使用- function-sections和-fdata-sections,然后链接时使用-Wl,——gc-sections,——print-gc-sections。链接器现在将列出所有可以删除的函数,因为它们从未被调用,以及所有从未被引用的全局函数。
(当然,你也可以跳过——print-gc-sections部分,让链接器无声地删除函数,但将它们保留在源代码中。)
注意:这只会发现未使用的完整函数,它不会对函数中的死代码做任何事情。在活函数中从死代码调用的函数也将被保留。
一些c++特有的特性也会导致问题,特别是:
Virtual functions. Without knowing which subclasses exist and which are actually instantiated at run time, you can't know which virtual functions you need to exist in the final program. The linker doesn't have enough information about that so it will have to keep all of them around. Globals with constructors, and their constructors. In general, the linker can't know that the constructor for a global doesn't have side effects, so it must run it. Obviously this means the global itself also needs to be kept.
在这两种情况下,虚函数或全局变量构造函数使用的任何东西都必须保留。
另外需要注意的是,如果您正在构建一个共享库,GCC中的默认设置将导出共享库中的每个函数,导致只要链接器就会“使用”它。为了解决这个问题,你需要将默认值设置为隐藏符号而不是导出(例如使用-fvisibility=hidden),然后显式地选择你需要导出的导出函数。
CppDepend是一个商业工具,它可以检测未使用的类型、方法和字段,以及做更多的事情。它适用于Windows和Linux(但目前不支持64位),并有两周的试用期。
免责声明:我不在那里工作,但我拥有这个工具的许可证(以及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] }
GNU链接器有一个——cref选项,用于生成交叉引用信息。你可以通过-Wl,——cref从gcc命令行传递它。
例如,假设foo。O定义了一个符号foo_sym,也在bar.o中使用。然后在输出中你会看到:
foo_sym foo.o
bar.o
如果foo_sym限制为foo。O,那么你不会看到任何额外的目标文件;后面会跟着另一个符号:
foo_sym foo.o
force_flag options.o
现在,从这里我们不知道是否使用了foo_sym。它只是一个候选:我们知道它是在一个文件中定义的,而没有在其他任何文件中使用。Foo_sym可以在foo中定义。O和用于那里。
那么,你该怎么处理这些信息呢
执行一些文本修改,以识别限制在一个目标文件中的这些符号,生成候选列表。 进入源代码,给每个候选对象提供带有静态的内部链接,就像它应该有的那样。 重新编译源代码。 现在,对于那些真正未使用的符号,编译器将能够警告,为你精确定位它们;你可以删除它们。
当然,我忽略了其中一些符号是故意不使用的可能性,因为它们是为动态链接而导出的(即使在链接可执行文件时也可能出现这种情况);这是一种更微妙的情况,你必须了解并明智地处理。
我不认为它可以自动完成。
即使使用代码覆盖工具,也需要提供足够的输入数据来运行。
可能是非常复杂和昂贵的静态分析工具,如Coverity的或LLVM编译器可能会有所帮助。
但我不确定,我更喜欢手动代码审查。
更新
嗯. .不过,仅删除未使用的变量和未使用的函数并不难。
更新
看了其他人的回答和评论后,我更加坚定地认为这是不可能的。
您必须了解代码以获得有意义的代码覆盖率度量,如果您知道大量的手动编辑将比准备/运行/检查覆盖率结果更快。
我有个朋友今天问了我这个问题,我看了看一些有前途的Clang开发,例如ASTMatchers和Static Analyzer,它们可能在编译过程中有足够的可见性来确定死代码部分,但后来我发现了这个:
https://blog.flameeyes.eu/2008/01/today-how-to-identify-unused-exported-functions-and-variables
它几乎完整地描述了如何使用一些GCC标志,这些标志似乎是为了识别未引用的符号而设计的!