查找未使用的包含标头的工具?

我知道 PC-Lint可以告诉你有关标题,包括但没有使用。有没有其他工具可以做到这一点,最好是在 Linux 上?

我们拥有一个庞大的代码库,在过去的15年里,我们看到了大量的功能移动,但是当功能从一个实现文件移动到另一个时,很少有剩余的 # include 指令被删除,到目前为止,留给我们的是一个相当混乱的局面。显然,我可以费力地删除所有 # include 指令,让编译器告诉我应该重新包含哪些指令,但我宁愿反过来解决这个问题——找到未使用的指令——而不是重新构建一个使用过的指令列表。

44168 次浏览

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

每个 cpp 文件
对于每个头包括
注释出包含
编译 cpp 文件
If (edit _ error)
取消标题的注释
别的
从 cpp 中删除头包含

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

我已经手动做了这个,它的价值在短期(哦,这是长期?由于编译时间缩短,需要很长时间):

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

它也是一个递归过程——每个保留在其中的头文件都需要检查,以确定是否可以删除 包含的任何头文件。另外,有时候可以用转发声明替换标头包含。

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

实际上,我有点讨厌 C + + 编译器,他们应该能够告诉你什么是不需要的-微软编译器可以告诉你什么时候改变头文件可以安全地忽略在编译过程中。

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

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

在评估一个工具时,你可以考虑以下几点:

对于函数重载,您希望包含重载的所有标头都是可见的,而不仅仅是包含通过重载解析选择的函数的标头:

// 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 &);

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

看看 德海德拉

网址:

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

应该可以编写一个脚本来检查未使用的 # include 文件。

如果您正在使用 Eclipse CDT,您可以尝试对 beta 测试人员免费的 包含器(在撰写本文时) ,并自动删除多余的 # include 或添加缺失的内容。

免责声明: 我工作的公司,开发包含器,并一直在使用它在过去的几个月。它对我很有效,所以试试吧: -)

谷歌的 Cppclean似乎在寻找未使用的头文件方面做得不错。我刚开始用。它会产生一些假阳性。它通常会发现头文件中没有必要的 include,但是它不会告诉你需要一个关联类的前向声明,而 include 需要移动到关联的源文件中。

这里有一个脚本可以做到这一点:

#!/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

如果有人感兴趣的话,我刚刚在 source 上安装了一个小的 Java 命令行工具来完成这项工作。 由于它是用 Java 编写的,所以显然在 Linux 上是可运行的。

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

如果您首先确保每个头文件都能自行编译,那么大多数移除未使用的方法都能更好地工作。我这样做的速度相对较快,如下所示(为打字错误道歉——我正在家里打字:

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 {} \;