75

我知道PC-Lint可以告诉您包含但未使用的标头。有没有其他工具可以做到这一点,最好是在 linux 上?

我们有一个庞大的代码库,在过去的 15 年中已经看到了大量的功能移动,但是当功能从一个实现文件移动到另一个实现文件时,很少会删除剩余的 #include 指令,这给我们留下了相当大的混乱。我显然可以做一些艰苦的事情,即删除所有#include 指令并让编译器告诉我要重新包含哪些指令,但我宁愿反过来解决问题——找到未使用的指令——而不是重建使用过的指令列表。

4

9 回答 9

31

免责声明:我的日常工作是为一家开发静态分析工具的公司工作。

如果大多数(如果不是全部)静态分析工具没有某种形式的标头使用检查,我会感到惊讶。您可以使用维基百科页面获取可用工具列表,然后通过电子邮件向公司询问。

在评估工具时可能会考虑以下几点:

对于函数重载,您希望所有包含重载的标头都可见,而不仅仅是包含由重载决议选择的函数的标头:

// f1.h
void foo (char);

// f2.h
void foo (int);


// bar.cc
#include "f1.h"
#include "f2.h"

int main ()
{
  foo (0);  // Calls 'foo(int)' but all functions were in overload set
}

如果您采用蛮力方法,首先删除所有标头然后重新添加它们直到它编译,如果首先添加'f1.h'然后代码将编译但程序的语义已经改变。

当你有部分和专业化时,类似的规则适用。是否选择专业并不重要,您需要确保所有专业都是可见的:

// f1.h
template <typename T>
void foo (T);

// f2.h
template <>
void foo (int);

// bar.cc
#include "f1.h"
#include "f2.h"


int main ()
{
  foo (0);  // Calls specialization 'foo<int>(int)'
}

至于重载示例,蛮力方法可能会导致程序仍然可以编译但具有不同的行为。

您可以注意的另一种相关分析类型是检查是否可以前向声明类型。考虑以下:

// A.h
class A { };

// foo.h
#include "A.h"
void foo (A const &);

// bar.cc
#include "foo.h"

void bar (A const & a)
{
  foo (a);
}

在上面的例子中,'A'的定义不是必需的,所以头文件'foo.h'可以被改变,使它有一个只针对'A'的前向声明:

// foo.h
class A;
void foo (A const &);

这种检查还减少了标头依赖性。

于 2009-08-20T09:23:44.290 回答
23

这是一个执行此操作的脚本:

#!/bin/bash
# prune include files one at a time, recompile, and put them back if it doesn't compile
# arguments are list of files to check
removeinclude() {
    file=$1
    header=$2
    perl -i -p -e 's+([ \t]*#include[ \t][ \t]*[\"\<]'$2'[\"\>])+//REMOVEINCLUDE $1+' $1
}
replaceinclude() {
   file=$1
   perl -i -p -e 's+//REMOVEINCLUDE ++' $1
}

for file in $*
do
    includes=`grep "^[ \t]*#include" $file | awk '{print $2;}' | sed 's/[\"\<\>]//g'`
    echo $includes
    for i in $includes
    do
        touch $file # just to be sure it recompiles
        removeinclude $file $i
        if make -j10 >/dev/null  2>&1;
        then
            grep -v REMOVEINCLUDE $file > tmp && mv tmp $file
            echo removed $i from $file
        else
            replaceinclude $file
            echo $i was needed in $file
        fi
    done
done
于 2011-08-21T00:13:51.673 回答
5

看看脱水

从网站:

Dehydra 是一种轻量级、可编写脚本的通用静态分析工具,能够对 C++ 代码进行特定于应用程序的分析。在最简单的意义上,Dehydra 可以被认为是一个语义 grep 工具。

应该可以提出一个检查未使用的#include 文件的脚本。

于 2010-02-23T20:52:53.720 回答
5

Google 的cppclean似乎在查找未使用的头文件方面做得不错。我刚开始使用它。它会产生一些误报。它经常会在头文件中发现不必要的包含,但它不会告诉您的是您需要关联类的前向声明,并且需要将包含移动到关联的源文件中。

于 2011-08-18T17:38:35.077 回答
3

如果您使用的是 Eclipse CDT,您可以尝试对 beta 测试人员免费的Includator(在撰写本文时),它会自动删除多余的 #include 或添加缺失的 #include。

免责声明:我为开发 Includator 的公司工作,过去几个月一直在使用它。它对我来说效果很好,所以试一试:-)

于 2011-06-01T13:36:00.007 回答
1

据我所知,没有一个(不是 PC-Lint),这是一种耻辱,也令人惊讶。我已经看到了做这个伪代码的建议(这基本上是自动化你的“艰苦过程”:

对于每个 cpp 文件
每个头包含
注释掉包含
编译 cpp 文件
if( compile_errors )
取消注释掉头
else
从 cpp 中删除头包含

把它放在每晚的 cron 中,它应该可以完成这项工作,使有问题的项目没有未使用的标头(显然,您总是可以手动运行它,但执行需要很长时间)。唯一的问题是不包含标头不会产生错误,但仍会产生代码。

于 2009-08-19T18:56:42.177 回答
1

由于减少了编译时间,我已经手动完成了这项工作,并且在短期内(哦,这是长期的吗? - 这需要很长时间)值得:

  1. 为每个 cpp 文件解析更少的标头。
  2. 更少的依赖——改变一个头文件后,整个世界都不需要重新编译。

它也是一个递归过程 - 每个保留的头文件都需要检查以查看是否可以删除它包含的任何头文件。另外,有时您可以用前向声明替换标题包含。

然后整个过程需要每隔几个月/每年重复一次,以保持在剩余的标题之上。

实际上,我对 C++ 编译器有点恼火,它们应该能够告诉您什么是不需要的——Microsoft 编译器可以告诉您何时可以在编译期间安全地忽略对头文件的更改。

于 2009-08-19T19:43:29.790 回答
0

如果有人感兴趣,我只是在 sourceforge 上安装了一个小型 Java 命令行工具来完成此操作。由于它是用 Java 编写的,因此显然可以在 linux 上运行。

该项目的链接是https://sourceforge.net/projects/chksem/files/chksem-1.0/

于 2013-08-16T07:41:55.563 回答
-1

如果您首先确保每个头文件都独立编译,则大多数删除未使用的包含的方法会更好。我按如下相对较快地完成了此操作(对错别字表示歉意——我在家里打字:

find . -name '*.h' -exec makeIncluder.sh {} \;

其中makeIncluder.sh包含:

#!/bin/sh
echo "#include \"$1\"" > $1.cpp

对于每个文件./subdir/classname.h,这种方法都会创建一个名为的文件,./subdir/classname.h.cpp其中包含该行

#include "./subdir/classname.h"

如果你makefile在 . 目录编译所有cpp文件并包含-I.,然后重新编译将测试每个包含文件是否可以自行编译。使用 goto-error 在您最喜欢的 IDE 中编译,并修复错误。

当你完成后,find . -name '*.h.cpp' -exec rm {} \;

于 2013-09-19T02:17:25.683 回答