it-swarm.cn

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

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

89
shambolic

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

您还可以查看pimpl习惯用法,以便减少头文件依赖性,从而更容易看到可以删除的内容。

45
Eclipse

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

28
Joe

有一个新的基于Clang的工具, 包括你使用的 ,旨在实现这一目标。

26
Josh Kelley

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

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

1)过载集:

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

2)模板专业化:

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

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

25
Richard Corden

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

假设您的源文件包含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时定义,因此工具需要知道编译器本身生成的指令以及编译命令中指定的指令而不是头文件。

10
Graeme Perrow

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


现在似乎Eric Raymond 有一个工具

Google的 cpplint.py 有一个“包括你使用的”规则(以及其他许多规则),但据我所知,没有“include only 你使用的是什么”。即便如此,它也很有用。

9
Max Lybbert

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

5
Adrian

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

4
Alex

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

4
Vladimir

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

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

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

3
Anna-Jayne Metcalfe

如果您的头文件通常以

#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文件导致多次引入标头。

3
Sam

如果您希望删除不必要的#include文件以减少构建时间,那么使用 cl.exe/MPmake -jXoreax可以更好地花时间和金钱来构建进程IncrediBuild ,distcc / 冰淇淋 等.

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

2
bk1e

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

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

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

2
selwyn

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

#define WIN32_LEAN_AND_MEAN
#define VC_EXTRALEAN

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

1
Roger Nelson

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

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

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

1
anon6439

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

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

1
Jean-Michaël Celerier

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

1
PeterSom

也许有点晚了,但我曾经发现一个WebKit Perl脚本可以完成你想要的。它需要一些适应我相信(我不熟悉Perl),但它应该做的伎俩:

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

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

0
rubenvb

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

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

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

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

  • 它构建好了。 string.h不是编译关键的,可以删除它的include。

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

  • 你得到一些其他编译错误。 string.h是必需的,并没有间接包含,所以包含是正确的开始。

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

在其他答案中提到的关于修改行为的标题而不是声明导致构建失败的事情的注释也适用于此处。

0
Britton Kerin

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

0
MSalters