查找包含未使用的标头的工具?[关闭]


73

我知道PC-Lint可以告诉您有关包含但不使用的标头的信息。还有其他工具可以做到这一点,最好是在Linux上吗?

我们有一个庞大的代码库,在过去的15年中,已经看到了很多功能在移动,但是当功能从一个实现文件转移到另一个实现文件时,剩下的#include指令很少被删除,到这一点为止,我们仍然一团糟。显然,我可以做些艰苦的工作,删除所有的#include指令,让编译器告诉我要重新包含的那些指令,但我宁愿反向解决问题-查找未使用的指令-而不是重建已使用的指令列表。


2
众所周知,很难找到不存在的东西。

这是我以前遇到的一个问题,但尚未找到100%可靠的自动化解决方案-我很想知道我们能得到什么答案。
DaveR

1
@Neil:总的来说是对的,但是在这种特定情况下,它并不那么难(抽象地讲)。您“仅”识别文件中的所有符号,将它们与满足它们的标头进行匹配,然后修剪掉该过程中未使用的标头。当然,实际上,这很复杂,因为您需要C / C ++解析器,并且“ required”的定义比使此过程“容易”的宽松。
尼克·巴斯汀,

1
@Nick,然后有仅在平台上使用或在某些配置下编译时使用的标头,您通过包含不应该直接包含客户端代码的私有标头来提供其所有符号的标头,而您的标头则包含要包含的另一个标头自给自足,但您不使用需要其他包含的接口,...
AProgrammer

@AProgrammer:仅在一个平台上使用相对容易解决-一种分析工具将以任何方式对其进行预处理(在“某些配置”情况下也应如此)。我不是在寻找文件中列出但经过适当预处理的标头-我在寻找的标头在完成的对象代码中包括完全不必要的源。另外,对于私有标头,这很好-在大多数情况下它们仍将被“使用”(或者它们是不必要的-要知道的有用的东西)。
尼克·巴斯汀,

Answers:


30

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

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

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

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

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

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


我看过的大多数内容都没有这种性质的标头使用情况检查。您对重载和专业化提出了很好的观点,但是值得庆幸的是,我们的约定是如此,以至于它们基本上永远不会位于不同的标头中。
尼克·巴斯汀

另外,我在维基百科页面上走的很远。C / C ++部分非常薄弱...我想我应该在商业提供商列表中查看,看看哪些支持C ++。另外,我对提出自己产品的建议非常满意-比我以前做的要多,而且您的建议总体上很有启发性。
尼克·巴斯汀

“对于函数重载,您希望所有包含重载的标头都可见,而不仅仅是包含由重载分辨率选择的函数的标头:...” +1,这是潜在的噩梦,这是调试的一个大原因,我担心自己做的事情
Andres Salas

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

我自己也使用过相同的方法,如果您使用的是GCC,请确保与之编译,-Werror=missing-prototypes否则您可以删除源文件中定义的函数的标头,这可能会在以后引起问题(如果标头不同步,您将不会注意到)。
ideaman42

真好!在一个大型项目上可能无法很好地扩展,但是对于一个小型项目我恰恰是需要的!谢谢!但是..我认为应该首先在all.h上运行,然后再在所有cpp上运行...
Stefano

这很棒。我不知道是否可以将这种蛮力解决方案扩展到处理上述stackoverflow.com/a/7111685/148668的注释中。
亚历克·雅各布森

5

看看Dehydra

从网站:

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

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


发展是在2010年放弃了
数学

开发更改为DXR
卡米尔·古德塞内

5

Google的cppclean似乎可以很好地找到未使用的头文件。我刚开始使用它。它产生一些误报。它通常会在头文件中找到不必要的包含,但是并不能告诉您,您需要关联类的前向声明,并且需要将包含移至关联的源文件中。


cppclean打扫太多。如果我有一个头文件foo.h明确使用inbar.h和in中定义的功能/类型,baz.h我希望看到foo.h一个#include "bar.h"和一个#include "baz.h"。假设这bar.h也发生了#include "baz.h"。这并不意味着我可以摆脱#include "baz.h"in foo.h。大多数不需要的头文件检查都会说我应该摆脱它。这是一个假阳性,几乎和假阴性一样严重。(而且可能更糟;误报太多,我将停止使用该工具。Lint是一个很好的例子。)
David Hammen

@David,我同意它会产生许多误报,但我认为它比手动检查每个文件要好,而且会迅速发现并纠正误报。您使用的东西效果很好吗?
机遇

3
今天我的档案我可以没有#include "baz.h"。明天也许不会。假设bar.h您有责任,您也正在删除不需要的标题。您bar.h不需要baz.h,因此您#include "baz.h"从中删除了多余的内容bar.h。您刚刚破坏了附带在此无关代码上的所有代码#include。解决的办法是不依赖这种背负。如果文件使用其他地方定义的某些功能,#include则定义该功能的文件。不要让其他标头#include为您做到这一点。
David Hammen

@DavidHammen,如果我不想依靠include背负,那不是像cppclean这样的工具吗?是的,如果我删除了一些不必要的标头,它将破坏很多东西,但实际上,这些文件不应该首先包含背负。因此,我去修复了那些依赖于这些文件的文件。如果我真的想要更好的依赖关系管理,那还是我应该做的。我很困惑,因为您说背负是不好的,但是然后您说不要使用强迫我消除它们的工具。
2012年

我认为您不了解问题。自动化工具有时会遗漏明显的内容,有时会显得太过分。他们给出错误的肯定和错误的否定。您可以使用这样的工具,但是请务必将它们与盐一同食用。
大卫·哈门

3

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

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


1

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

对于每个
头文件的每个cpp文件,包括
注释掉包括在内的内容
编译cpp文件
if(compile_errors)
取消注释头文件,
否则
从cpp中删除头文件包含

将其放在每晚的cron中,它应该可以完成工作,使有问题的项目中没有未使用的头文件(显然,您始终可以手动运行它,但是执行起来会花费很长时间)。唯一的问题是,不包含标头不会产生错误,但仍会产生代码。


1
不幸的是,这仍然无法清除包含不需要的其他头文件的头文件(更糟糕的是,可能会导致其他实现文件中的“巧合编程”,这些实现文件通过实际上不需要它们的其他头文件来获取所需的头文件) )。它至少减少了cpp文件中虚假包含的数量,但我也想在其他标头中消除它们。
尼克·巴斯汀,

3
也不建议删除每个标头。考虑#include <vector>和#include <algorithm>。在某些实现中,将包含矢量算法,但不能保证。健壮的代码应同时包含两者(如果同时使用)。您描述的方法可以根据vector的实现删除#include <algorithm>。
deft_code

这是真的。尼克,您是否更关心本地头文件(或者您至少有很多头文件)?如果是这样,您可以修改上述算法,以免弄乱库标题,并手动进行调整。这很痛苦,但是至少会减少工作量。
Cinder6

1

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

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

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

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

实际上,我对C ++编译器感到有些恼火,他们应该能够告诉您不需要的内容-Microsoft编译器可以告诉您何时可以在编译期间安全地忽略对头文件的更改。



-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文件和contains -I.,然后重新编译将测试每个include文件是否可以自行编译。使用goto-error在您喜欢的IDE中进行编译,然后修复错误。

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


1
我看不出这有什么帮助。即使我的某些标头不是自己编译的(无论如何也不是),这不会为未使用的标头提供任何其他信息。
尼克·巴斯汀
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.