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

时间:2008-09-16 16:38:55

标签: c++ visual-studio-2008 include header dependencies

我正在研究Visual Studio 2008中的一个大型C ++项目,并且有很多文件带有不必要的#include指令。有时#include只是工件,一切都会被删除,但是在其他情况下,可以向前声明类,并且可以将#include移动到.cpp文件。是否有任何好的工具可以检测这两种情况?

20 个答案:

答案 0 :(得分:45)

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

你也可以看一下pimpl习语,让你减少头文件的依赖性,以便更容易看到你可以删除的内容。

答案 1 :(得分:28)

PC Lint对此非常有效,它也会为你找到各种其他愚蠢的问题。它具有可用于在Visual Studio中创建外部工具的命令行选项,但我发现Visual Lint插件更易于使用。即使是免费版的Visual Lint也有帮助。但给PC-Lint一个机会。配置它以便它不会给你太多的警告需要一点时间,但你会惊讶于它出现了什么。

答案 2 :(得分:26)

有一个新的基于Clang的工具include-what-you-use,旨在实现这一目标。

答案 3 :(得分:25)

!!免责声明!!我从事商业静态分析工具(不是PC Lint)。 !!声明!!

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

1)重载集:

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

2)模板专业化:

与重载示例类似,如果您对模板有部分或显式特化,则希望在使用模板时它们都可见。可能是主模板的特化是在不同的头文件中。使用特化删除标头不会导致编译错误,但如果选择了该特化,则可能导致未定义的行为。 (见:Visibility of template specialization of C++ function

正如'msalters'所指出的,对代码进行全面分析还可以分析类的使用情况。通过检查文件的特定路径如何使用类,有可能可以完全删除类的定义(以及它的所有依赖性),或者至少移动到更接近包含主要源的级别树。

答案 4 :(得分:10)

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

假设您的源文件包含a.h和b.h; a.h包含#define USE_FEATURE_X,b.h使用#ifdef USE_FEATURE_X。如果#include "a.h"被注释掉,您的文件仍然可以编译,但可能无法按预期执行。以编程方式检测 是非常重要的。

无论使用什么工具,您都需要了解构建环境。如果a.h看起来像:

#if defined( WINNT )
   #define USE_FEATURE_X
#endif

然后USE_FEATURE_X仅在定义WINNT时定义,因此工具需要知道编译器本身生成的指令以及编译命令中指定的指令而不是头文件。

答案 5 :(得分:9)

像Timmermans一样,我对此并不熟悉任何工具。但我知道编写Perl(或Python)脚本的程序员一次尝试注释掉每个include行,然后编译每个文件。


现在看来Eric Raymond has a tool for this

Google的cpplint.py有一个“包含您使用的”规则(以及其他许多规则),但据我所知,没有“仅包含 您使用的内容”。即便如此,它也很有用。

答案 6 :(得分:5)

如果您对此主题感兴趣,可能需要查看Lakos'Large Scale C++ Software Design。这有点过时了,但是会遇到许多“物理设计”问题,例如找到需要包含的标题的绝对最小值。我还没有真正在其他任何地方看到这种事情。

答案 7 :(得分:4)

您可以使用C/C++ Include File Dependencies Watcher构建包含图表,并直观地查找不需要的内容。

答案 8 :(得分:4)

尝试Include Manager。它可以在Visual Studio中轻松集成,并可视化您的包含路径,帮助您查找不必要的东西。 在内部,它使用Graphviz,但还有许多很酷的功能。虽然它是商业产品,但价格非常低廉。

答案 9 :(得分: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文件导致多次引入标头。

答案 10 :(得分:3)

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

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

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

答案 11 :(得分:2)

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

对于每个包含和源文件,一次注释掉每个包含文件,看看它是否编译。

按字母顺序对包含文件进行排序也是一个好主意,如果无法做到这一点,请添加注释。

答案 12 :(得分:2)

如果您希望删除不必要的#include文件以减少构建时间,那么使用cl.exe /MPmake -j,{{ 3}},distcc / Xoreax IncrediBuild

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

答案 13 :(得分:1)

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

http://msdn.microsoft.com/en-us/library/szfdksca(VS.71).aspx

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

答案 14 :(得分:1)

添加以下一个或两个#defines 将经常排除不必要的头文件和 可以大大改善 编译时,特别是如果代码没有使用Windows API函数。

#define WIN32_LEAN_AND_MEAN
#define VC_EXTRALEAN

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

答案 15 :(得分:1)

如果您使用Eclipse CDT,可以试用http://includator.com来优化您的包含结构。但是,Includator可能对VC ++的预定义包含知识不够,并且设置CDT以使用VC ++而且还没有内置到CDT中。

答案 16 :(得分:1)

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

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

答案 17 :(得分:0)

现有的一些答案表明这很难。确实如此,因为您需要一个完整的编译器来检测前向声明适合的情况。你不能解析C ++而不知道符号是什么意思;语法对此来说太模糊了。您必须知道某个名称是否命名一个类(可以是前向声明的)还是一个变量(不能)。此外,您需要知道名称空间。

答案 18 :(得分:0)

也许有点晚了,但我曾经发现一个WebKit perl脚本可以完成您想要的任务。它需要一些适应我相信(我不是很精通perl),但它应该做的伎俩:

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

(这是一个旧分支,因为trunk不再有文件)

答案 19 :(得分:0)

如果您认为不再需要特定标题(比如说) string.h),你可以注释掉包括然后把它放在所有的下面 包括:

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

当然,您的接口标头可能使用不同的#define约定 记录它们包含在CPP记忆中。或者没有惯例,在这种情况下 这种方法不起作用。

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

  • 它构建正常。 string.h不是编译关键的,包含它 可以删除。

  • #error之旅。 string.g以某种方式间接包含在内 您还不知道是否需要string.h。如果需要,你 应直接#include它(见下文)。

  • 您收到其他一些编译错误。 string.h是必要的,而且不存在 间接包含,所以包含是正确的开始。

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

其他答案中提到的关于修改行为的标题的警告 相反,声明导致构建失败的事情也适用于此。