关于Visual Studio 2008:我应该如何在大型C ++项目中检测到不必要的#include文件?

关于Visual Studio 2008:我应该如何在大型C ++项目中检测到不必要的#include文件?

How should I detect unnecessary #include files in a large C++ project?

我正在Visual Studio 2008中从事大型C ++项目,并且有很多文件带有不必要的#include指令。 有时#include只是工件,删除它们后一切都会很好地编译,在其他情况下,可以向前声明类并将#include移到.cpp文件中。 是否有检测这些情况的良好工具?


虽然它不会显示不需要的包含文件,但Visual Studio具有设置/showIncludes(右键单击.cpp文件,Properties->C/C++->Advanced),该设置将在编译时输出所有包含文件的树。这可以帮助识别不需要包含的文件。

您还可以看一下pimpl习惯用法,以减少对头文件的依赖,从而更轻松地查看可以删除的内容。


PC Lint对此非常有效,它也为您找到了其他各种愚蠢的问题。它具有可用于在Visual Studio中创建外部工具的命令行选项,但我发现Visual Lint加载项更易于使用。甚至Visual Lint的免费版本也有帮助。但是,试试PC-Lint。配置它使其不会给您太多警告会花费一些时间,但是您会惊讶于它的出现。


有一个新的基于Clang的工具,包括您所使用的工具,旨在实现此目的。


免责声明!我使用的是商用静态分析工具(不是PC Lint)。免责声明!

简单的非解析方法存在几个问题:

1)过载设置:

重载函数可能具有来自不同文件的声明。删除一个头文件可能导致选择了不同的重载,而不是编译错误!结果将是语义上的无声更改,此后可能很难跟踪。

2)模板专长:

与重载示例类似,如果您对模板有部分或明确的专长,则希望在使用模板时将它们全部可见。主模板的专门化可能在不同的头文件中。删除带有特殊化的标题不会导致编译错误,但是如果选择了该特殊化,则可能导致不确定的行为。 (请参阅:C ++函数模板专业化的可见性)

正如" msalters"指出的那样,对代码执行完整的分析还可以分析类的用法。通过检查如何通过特定的文件路径使用类,可以完全删除该类的定义(以及所有其相关性),或者至少将其移到更接近include中主要源的级别。树。


我不知道有任何这样的工具,并且我曾经考虑过编写一个工具,但是事实证明,这是一个很难解决的问题。

假设您的源文件包含a.h和b.h; a.h包含#define USE_FEATURE_X,b.h使用#ifdef USE_FEATURE_X。如果#include"a.h"被注释掉,您的文件可能仍会编译,但可能无法满足您的期望。以编程方式检测到这一点并非易事。

无论使用哪种工具,都需要知道您的构建环境。如果a.h看起来像:

1
2
3
#if defined( WINNT )
   #define USE_FEATURE_X
#endif

然后,只有在定义了WINNT的情况下,才定义USE_FEATURE_X,因此该工具将需要知道编译器本身生成了哪些指令,以及哪些指令是在compile命令而不是头文件中指定的。


像Timmermans一样,我对任何工具都不熟悉。但是我知道有一些程序员编写了Perl(或Python)脚本,试图一次注释掉每个包含行,然后编译每个文件。

看来现在Eric Raymond拥有用于此的工具。

Google的cpplint.py有一个"包含您使用的内容"规则(还有许多其他规则),但据我所知,没有"仅包含您使用的内容"规则。即使这样,它还是有用的。


一般而言,如果您对此主题感兴趣,则可以查看Lakos的大规模C ++软件设计。这有些陈旧,但是会遇到很多"物理设计"问题,例如找到需要包含的标头的绝对最小值。我还没有真正在其他地方看到过这种讨论。


您可以使用C / C ++ Include File Dependencies Watcher构建一个包含图,并在视觉上找到不需要的包含。


尝试包括管理器。它可以轻松集成到Visual Studio中,并可视化您的包含路径,这有助于您查找不必要的内容。
在内部,它使用Graphviz,但还有许多更酷的功能。尽管它是一种商业产品,但价格却非常低廉。


如果您的头文件通常以

1
2
3
4
#ifndef __SOMEHEADER_H__
#define __SOMEHEADER_H__
// header contents
#endif

(而不是一次使用#pragma),您可以将其更改为:

1
2
3
4
5
6
#ifndef __SOMEHEADER_H__
#define __SOMEHEADER_H__
// header contents
#else
#pragma message("Someheader.h superfluously included")
#endif

而且由于编译器会输出正在编译的cpp文件的名称,因此至少可以让您知道哪个cpp文件导致多次引入标头。


PC-Lint确实可以做到这一点。一种简单的方法是将其配置为仅检测未使用的包含文件并忽略所有其他问题。这非常简单-仅启用消息766("模块中未使用头文件"),只需在命令行中包含选项-w0 + e766。

相同的方法也可以用于相关消息,例如964("模块中不直接使用头文件")和966("模块中不直接使用间接包含的头文件")。

我在上周的博客文章http://www.riverblade.co.uk/blog.php?archive=2008_09_01_archive.xml#3575027665614976318中更详细地介绍了FWIW。


如果您要删除不必要的#include文件以减少构建时间,则可能会花费更多的时间和金钱来使用cl.exe / MP,make -j,Xoreax IncrediBuild,distcc / icecream等并行化构建过程。

当然,如果您已经有一个并行的构建过程,并且仍在尝试加快它的速度,那么一定要清理您的#include指令并删除那些不必要的依赖项。


从每个包含文件开始,并确保每个包含文件仅包含编译自身所需的内容。然后,C ++文件缺少的所有包含文件都可以添加到C ++文件本身。

对于每个包含文件和源文件,一次注释掉每个包含文件,然后查看其是否可以编译。

将包含文件按字母顺序排序也是一个好主意,如果不可能,请添加注释。


最新的Jetbrains IDE CLion自动显示(灰色)当前文件中未使用的包含项。

也可以从IDE中获取所有未使用的包含项(以及函数,方法等)的列表。


添加以下一个或两个#defines
将经常排除不必要的头文件和
可能会大大改善
尤其是在未使用Windows API函数的代码的情况下编译时间。

1
2
#define WIN32_LEAN_AND_MEAN
#define VC_EXTRALEAN

请参阅http://support.microsoft.com/kb/166474


如果您要使用Eclipse CDT,则可以尝试http://includator.com来优化您的包含结构。但是,Includator可能对VC ++的预定义包含并不足够了解,并且CDT尚未内置CDT以将VC ++与正确的包含一起使用。


如果尚未使用,则使用预编译的头文件包含所有您不会更改的内容(平台头文件,外部SDK头文件或项目的静态已完成部分)将在构建时间上产生巨大差异。

http://msdn.microsoft.com/zh-CN/library/szfdksca(VS.71).aspx

另外,尽管对您的项目来说可能为时已晚,但将项目组织成多个部分,并且不将所有本地标头集中到一个大的主标头中,是一个好习惯,尽管这需要一些额外的工作。


现有的一些答案表明这很困难。的确如此,因为您需要一个完整的编译器来检测前向声明是适当的情况。您无法在不了解符号含义的情况下解析C ++。语法太含糊了。您必须知道某个名称是命名一个类(可以被向前声明)还是一个变量(不能)。另外,您需要了解名称空间。


也许有点晚了,但是我曾经发现一个WebKit perl脚本可以完成您想要的操作。我相信它需要一些调整(我不太熟悉perl),但是应该可以解决问题:

http://trac.webkit.org/browser/branches/old/safari-3-2-branch/WebKitTools/Scripts/find-extra-includes

(这是一个旧的分支,因为中继不再具有该文件)


如果有一个您认为不再需要的特定标头(例如,
string.h),您可以注释掉其中的内容,然后将其放在所有
包括:

1
2
3
#ifdef _STRING_H_
#  error string.h is included indirectly
#endif

当然,您的接口标头可能会使用其他#define约定
记录它们包含在CPP内存中。或没有约定,在这种情况下
这种方法行不通。

然后重建。有三种可能性:

  • 一切正常。 string.h不是至关重要的,包括它
    可以删除。

  • 错误之旅。 string.g以某种方式间接包含
    您仍然不知道是否需要string.h。如果需要,您
    应该直接#include它(见下文)。

  • 您还会遇到其他一些编译错误。 string.h是必需的,不是
    是间接包含的,因此包含开始是正确的。

请注意,取决于.h或.c直接使用时的间接包含
另一个.h几乎可以肯定是一个错误:您实际上是在保证您的
只要您正在使用其他标头,代码就只需要该标头
需要它,这可能不是您的意思。

其他答案中有关修改行为的标头的注意事项
而是声明导致构建失败的内容也适用于此。


推荐阅读