如何在大型C ++项目中检测不必要的#include文件?


96

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

Answers:


50

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

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


1
/ showincludes很棒。没有这些,手动执行此操作会令人生畏。
shambolic

30

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


3
关于如何使用PC -皮棉做一些指令可以在这里找到riverblade.co.uk/...
大卫·赛克斯


26

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

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

1)过载设置:

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

2)模板专长:

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

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


@RichardCorden:您的软件(QA C ++)太昂贵了。
Xander Tulip

13
@XanderTulip:很难在没有最终推销的情况下对此做出回应-因此我提前致歉。恕我直言,您需要考虑的是,一个好的工程师要花多长时间才能在任何合理规模的项目中找到类似的东西(以及许多其他语言/控制流错误)。随着软件的更改,相同的任务需要一次又一次地重复。因此,当您计算出节省的时间量时,该工具的成本可能并不重要。
理查德·科登

10

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

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

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

#if defined( WINNT )
   #define USE_FEATURE_X
#endif

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


9

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


看来现在Eric Raymond 拥有了一个工具

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


我读这本书的时候不得不笑。上个月,我的老板在我们的一个项目中做了这件事。标头减少包括两个因素。
唐·韦克菲尔德

2
Mac上的codewarrior曾经内置一个脚本来执行此操作,然后注释掉,编译,在未注释错误时继续执行#includes末尾。它仅适用于文件顶部的#include,但这通常是它们所在的位置。它不是完美的,但是确实可以使事情保持理智。
slycrel

5

一般而言,如果您对该主题感兴趣,则可能需要查看Lakos的Large C ++ Software Design。这有些陈旧,但是会涉及很多“物理设计”问题,例如找到需要包含的标头的绝对最小值。我还没有真正在其他地方看到过这种讨论。


4

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



3

如果您的头文件通常以

#ifndef __SOMEHEADER_H__
#define __SOMEHEADER_H__
// header contents
#endif

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

#ifndef __SOMEHEADER_H__
#define __SOMEHEADER_H__
// header contents
#else 
#pragma message("Someheader.h superfluously included")
#endif

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


12
我认为可以多次包含标头。最好包含您使用的内容,而不要依赖于包含文件。我认为OP希望找到的是未真正使用的#include。
瑞安·金斯特罗姆

12
IMO积极做错事。标头应包含其他标头,如果没有它们将无法使用。并且当您同时拥有A.h和且B.hC.h包含A.h和时B.h,您需要包含和,因为您同时需要包含和,所以包含C.h两次,但这很好,因为编译器将第二次跳过它,如果没有,则必须记住总是C.h在之前A.hB.h最后包含更多无用的内容。
Jan Hudec

5
内容准确,这是查找包含多次的标头的良好解决方案。但是,最初的问题不能由此得到回答,我无法想象这何时是个好主意。Cpp文件应包含它们依赖的所有标头,即使标头包含在其他位置之前也是如此。您不希望您的项目是特定于编译顺序的,或者不希望其他标题包含所需的标题。
jaypb

3

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

相同的方法也可以与相关消息一起使用,例如964(“模块中未直接使用头文件”)和966(“模块中未直接使用间接包含的头文件”)。

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


2

如果您要删除不必要的#include文件以减少构建时间,则最好使用cl.exe / MPmake -jXoreax IncrediBuild,distcc / icecream等并行化构建过程,以节省时间和金钱。

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


2

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

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

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


2
如果涉及大量的实现文件,我不确定此注释的实用性。
桑尼2012年

1

添加以下#define中的一个或两个都将通常排除不必要的头文件,并且可能会大大缩短编译时间,尤其是在代码未使用Windows API函数的情况下。

#define WIN32_LEAN_AND_MEAN
#define VC_EXTRALEAN

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


1
两者都不需要-VC_EXTRALEAN定义WIN32_LEAN_AND_MEAN
Aidan Ryan

1

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

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

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


预编译头的详细说明:cygnus-software.com/papers/precompiledheaders.html(不确定自动生成的预编译头在Visual Studio的最新版本中是否已损坏,但值得检查。)
idbrii 2010年

1

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


1

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

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


0

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


您可以只说“确定必需的#include等同于解决暂停问题。祝您好运:)”当然,您可以使用试探法,但是我不知道有任何免费软件可以做到这一点。
porges


0

如果有您认为不再需要的特定标头(例如string.h),则可以注释掉包含,然后将其放在所有包含下面:

#ifdef _STRING_H_
#  error string.h is included indirectly
#endif

当然,您的接口头可能会使用不同的#define约定来将其包含在CPP内存中。或者没有约定,在这种情况下,这种方法将行不通。

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

  • 一切正常。string.h不是关键的编译对象,可以删除其中的include。

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

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

请注意,当您的.h或.c直接使用另一个.h时,依赖于间接包含几乎是一个错误:您实际上承诺,只要您使用的其他某些头文件要求您的代码,该代码将只需要该头文件,这可能不是你的意思。

在其他答案中提到的有关标题修改行为的警告,也适用于声明导致构建失败的内容的声明。

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.