我知道PC-Lint可以告诉您有关包含但不使用的标头的信息。还有其他工具可以做到这一点,最好是在Linux上吗?
我们有一个庞大的代码库,在过去的15年中,已经看到了很多功能在移动,但是当功能从一个实现文件转移到另一个实现文件时,剩下的#include指令很少被删除,到这一点为止,我们仍然一团糟。显然,我可以做些艰苦的工作,删除所有的#include指令,让编译器告诉我要重新包含的那些指令,但我宁愿反向解决问题-查找未使用的指令-而不是重建已使用的指令列表。
我知道PC-Lint可以告诉您有关包含但不使用的标头的信息。还有其他工具可以做到这一点,最好是在Linux上吗?
我们有一个庞大的代码库,在过去的15年中,已经看到了很多功能在移动,但是当功能从一个实现文件转移到另一个实现文件时,剩下的#include指令很少被删除,到这一点为止,我们仍然一团糟。显然,我可以做些艰苦的工作,删除所有的#include指令,让编译器告诉我要重新包含的那些指令,但我宁愿反向解决问题-查找未使用的指令-而不是重建已使用的指令列表。
Answers:
免责声明:我的日常工作是在一家开发静态分析工具的公司工作。
如果大多数(如果不是全部)静态分析工具没有某种形式的标题使用检查,我会感到惊讶。您可以使用此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 &);
这种检查还减少了标头依赖性。
这是执行此操作的脚本:
#!/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
-Werror=missing-prototypes
否则您可以删除源文件中定义的函数的标头,这可能会在以后引起问题(如果标头不同步,您将不会注意到)。
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是一个很好的例子。)
#include "baz.h"
。明天也许不会。假设bar.h
您有责任,您也正在删除不需要的标题。您bar.h
不需要baz.h
,因此您#include "baz.h"
从中删除了多余的内容bar.h
。您刚刚破坏了附带在此无关代码上的所有代码#include
。解决的办法是不依赖这种背负。如果文件使用其他地方定义的某些功能,#include
则定义该功能的文件。不要让其他标头#include
为您做到这一点。
如果您使用的是Eclipse CDT,可以尝试使用Beta版测试人员免费使用的Includator(在撰写本文时),并自动删除多余的#include或添加缺失的#include。
免责声明:我为开发Includator的公司工作,并且过去几个月一直在使用它。它对我来说效果很好,所以尝试一下:-)
据我所知,没有一个(不是PC-Lint),这是一个耻辱,令人惊讶。我已经看到了执行此伪代码的建议(基本上是使您的“艰苦过程”自动化:
对于每个
头文件的每个cpp文件,包括
注释掉包括在内的内容
编译cpp文件
if(compile_errors)
取消注释头文件,
否则
从cpp中删除头文件包含
将其放在每晚的cron中,它应该可以完成工作,使有问题的项目中没有未使用的头文件(显然,您始终可以手动运行它,但是执行起来会花费很长时间)。唯一的问题是,不包含标头不会产生错误,但仍会产生代码。
如果有人感兴趣,我只是放上sourceforge上的一个小型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文件和contains -I.
,然后重新编译将测试每个include文件是否可以自行编译。使用goto-error在您喜欢的IDE中进行编译,然后修复错误。
完成后, find . -name '*.h.cpp' -exec rm {} \;