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

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


当前回答

真正的答案是:你永远无法真正确定。

至少,对于重要的情况,你不能确定你已经得到了全部。考虑以下来自维基百科关于不可达代码的文章:

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

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

int y;
cin >> y;
double x = sqrt((double)y);

if (x != 0 && x < 1)
{
  doStuff();
}

Will the compiler catch this? Maybe. But to do that, it will need to do more than run sqrt against a constant scalar value. It will have to figure out that (double)y will always be an integer (easy), and then understand the mathematical range of sqrt for the set of integers (hard). A very sophisticated compiler might be able to do this for the sqrt function, or for every function in math.h, or for any fixed-input function whose domain it can figure out. This gets very, very complex, and the complexity is basically limitless. You can keep adding layers of sophistication to your compiler, but there will always be a way to sneak in some code that will be unreachable for any given set of inputs.

还有一些输入集是永远不会被输入的。输入在现实生活中没有意义,或者在其他地方被验证逻辑阻塞。编译器没有办法知道这些。

这样做的最终结果是,虽然其他人提到的软件工具非常有用,但您永远无法确定您捕获了所有内容,除非您随后手动检查代码。即便如此,你也无法确定自己是否错过了什么。

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

其他回答

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

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

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

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

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

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

根据文档:

每当变量未使用时发出警告 除了它的声明,无论何时 函数被声明为静态的,但是 从来没有定义,每当一个标签 声明但未使用,并且每当a 语句计算的结果为 明确没有使用。

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

编辑:这里是另一个有用的标志-Wunreachable-code 根据文档:

此选项用于在编译器检测到至少一整行源代码永远不会执行时发出警告,因为某些条件永远不会满足,或者因为它在一个永远不会返回的过程之后。

更新:我在遗留的C/ c++项目中发现了类似的主题死代码检测

这取决于创建应用程序时使用的平台。

例如,如果你使用Visual Studio,你可以使用像. net ANTS Profiler这样的工具来解析和分析你的代码。通过这种方式,您应该很快知道实际使用了代码的哪一部分。Eclipse也有等效的插件。

否则,如果您需要知道最终用户实际使用了应用程序的哪些功能,并且您可以轻松地发布应用程序,则可以使用日志文件进行审计。

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

Mark as much public functions and variables as private or protected without causing compilation error, while doing this, try to also refactor the code. By making functions private and to some extent protected, you reduced your search area since private functions can only be called from the same class (unless there are stupid macro or other tricks to circumvent access restriction, and if that's the case I'd recommend you find a new job). It is much easier to determine that you don't need a private function since only the class you're currently working on can call this function. This method is easier if your code base have small classes and is loosely coupled. If your code base does not have small classes or have very tight coupling, I suggest cleaning those up first.

接下来将标记所有剩余的公共函数,并制作一个调用图,以找出类之间的关系。从这棵树上,试着找出树枝的哪一部分看起来可以修剪。

这种方法的优点是你可以在每个模块的基础上进行测试,所以当你的代码库损坏时,你很容易通过单元测试,而不会有很长一段时间。