我有遗留的c++代码,我应该删除不使用的代码。问题是代码库很大。

我如何才能知道哪些代码从未被调用/从未被使用?


当前回答

我通常找没用的东西的方法是

确保构建系统正确地处理依赖项跟踪 设置第二个监视器,使用全屏终端窗口,运行重复构建并显示第一个满屏的输出。watch "make 2>&1"倾向于在Unix上做到这一点。 在整个源代码树上运行查找和替换操作,添加“//?”“在每一行的开头 通过删除相应行中的"//?"来修复编译器标记的第一个错误。 重复操作,直到没有错误。

这是一个有点漫长的过程,但确实能得到很好的结果。

其他回答

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和用于那里。

那么,你该怎么处理这些信息呢

执行一些文本修改,以识别限制在一个目标文件中的这些符号,生成候选列表。 进入源代码,给每个候选对象提供带有静态的内部链接,就像它应该有的那样。 重新编译源代码。 现在,对于那些真正未使用的符号,编译器将能够警告,为你精确定位它们;你可以删除它们。

当然,我忽略了其中一些符号是故意不使用的可能性,因为它们是为动态链接而导出的(即使在链接可执行文件时也可能出现这种情况);这是一种更微妙的情况,你必须了解并明智地处理。

我通常找没用的东西的方法是

确保构建系统正确地处理依赖项跟踪 设置第二个监视器,使用全屏终端窗口,运行重复构建并显示第一个满屏的输出。watch "make 2>&1"倾向于在Unix上做到这一点。 在整个源代码树上运行查找和替换操作,添加“//?”“在每一行的开头 通过删除相应行中的"//?"来修复编译器标记的第一个错误。 重复操作,直到没有错误。

这是一个有点漫长的过程,但确实能得到很好的结果。

一种方法是使用调试器和编译器特性,在编译过程中消除未使用的机器代码。

一旦某些机器代码被删除,调试器就不会让你在相应的源代码行上添加断点。因此,您将断点放置在任何地方,并启动程序并检查断点——那些处于“此源未加载代码”状态的断点对应于已消除的代码——要么该代码从未被调用,要么已内联,您必须执行一些最小分析,以找出这两者中哪一个发生了。

至少这是它在Visual Studio中的工作方式,我猜其他工具集也可以做到这一点。

这需要做很多工作,但我认为比手动分析所有代码要快。

我有个朋友今天问了我这个问题,我看了看一些有前途的Clang开发,例如ASTMatchers和Static Analyzer,它们可能在编译过程中有足够的可见性来确定死代码部分,但后来我发现了这个:

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

它几乎完整地描述了如何使用一些GCC标志,这些标志似乎是为了识别未引用的符号而设计的!

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] }