简体   繁体   English

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

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

I am working on a large C++ project in Visual Studio 2008, and there are a lot of files with unnecessary #include directives. 我正在研究Visual Studio 2008中的一个大型C ++项目,并且有很多文件带有不必要的#include指令。 Sometimes the #include s are just artifacts and everything will compile fine with them removed, and in other cases classes could be forward declared and the #include could be moved to the .cpp file. 有时#include只是工件,一切都会被删除,但是在其他情况下,类可以向前声明,#include可以移动到.cpp文件。 Are there any good tools for detecting both of these cases? 是否有任何好的工具可以检测这两种情况?

While it won't reveal unneeded include files, Visual studio has a setting /showIncludes (right click on a .cpp file, Properties->C/C++->Advanced ) that will output a tree of all included files at compile time. 虽然它不会显示不需要的包含文件,但Visual Studio有一个设置/showIncludes (右键单击.cpp文件, Properties->C/C++->Advanced ),它将在编译时输出所有包含文件的树。 This can help in identifying files that shouldn't need to be included. 这有助于识别不需要包含的文件。

You can also take a look at the pimpl idiom to let you get away with fewer header file dependencies to make it easier to see the cruft that you can remove. 您还可以查看pimpl习惯用法,以便减少头文件依赖性,从而更容易看到可以删除的内容。

PC Lint works quite well for this, and it finds all sorts of other goofy problems for you too. PC Lint对此非常有效,它也为您找到各种其他愚蠢的问题。 It has command line options that can be used to create External Tools in Visual Studio, but I've found that the Visual Lint addin is easier to work with. 它具有可用于在Visual Studio中创建外部工具的命令行选项,但我发现Visual Lint插件更易于使用。 Even the free version of Visual Lint helps. 即使是免费版的Visual Lint也有帮助。 But give PC-Lint a shot. 但给PC-Lint一个机会。 Configuring it so it doesn't give you too many warnings takes a bit of time, but you'll be amazed at what it turns up. 配置它以便它不会给你太多的警告需要一点时间,但你会惊讶于它出现了什么。

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

!!DISCLAIMER!! !!免责声明! I work on a commercial static analysis tool (not PC Lint). 我从事商业静态分析工具(不是PC Lint)。 !!DISCLAIMER!! !!免责声明!

There are several issues with a simple non parsing approach: 简单的非解析方法有几个问题:

1) Overload Sets: 1)过载集:

It's possible that an overloaded function has declarations that come from different files. 重载函数可能具有来自不同文件的声明。 It might be that removing one header file results in a different overload being chosen rather than a compile error! 可能是删除一个头文件会导致选择不同的重载而不是编译错误! The result will be a silent change in semantics that may be very difficult to track down afterwards. 结果将是语义的静默变化,之后可能很难跟踪。

2) Template specializations: 2)模板专业化:

Similar to the overload example, if you have partial or explicit specializations for a template you want them all to be visible when the template is used. 与重载示例类似,如果您对模板有部分或显式特化,则希望在使用模板时它们都可见。 It might be that specializations for the primary template are in different header files. 可能是主模板的特化是在不同的头文件中。 Removing the header with the specialization will not cause a compile error, but may result in undefined behaviour if that specialization would have been selected. 使用特化删除标头不会导致编译错误,但如果选择了该特化,则可能导致未定义的行为。 (See: Visibility of template specialization of C++ function ) (参见: C ++函数模板特化的可见性

As pointed out by 'msalters', performing a full analysis of the code also allows for analysis of class usage. 正如'msalters'所指出的那样,对代码进行全面分析还可以分析类的使用情况。 By checking how a class is used though a specific path of files, it is possible that the definition of the class (and therefore all of its dependnecies) can be removed completely or at least moved to a level closer to the main source in the include tree. 通过检查文件的特定路径如何使用类,有可能可以完全删除类的定义(以及它的所有依赖性),或者至少移动到更接近包含主要源的级别树。

I don't know of any such tools, and I have thought about writing one in the past, but it turns out that this is a difficult problem to solve. 我不知道有任何这样的工具,我曾考虑过写一个这样的工具,但事实证明这是一个难以解决的问题。

Say your source file includes ah and bh; 说你的源文件包含啊和bh; ah contains #define USE_FEATURE_X and bh uses #ifdef USE_FEATURE_X . 啊包含#define USE_FEATURE_X ,bh使用#ifdef USE_FEATURE_X If #include "ah" is commented out, your file may still compile, but may not do what you expect. 如果注释掉了#include "ah" ,那么您的文件仍然可以编译,但可能无法达到预期效果。 Detecting this programatically is non-trivial. 编程方式检测这一点并非易事

Whatever tool does this would need to know your build environment as well. 无论使用什么工具,您都需要了解构建环境。 If ah looks like: 如果啊看起来像:

#if defined( WINNT )
   #define USE_FEATURE_X
#endif

Then USE_FEATURE_X is only defined if WINNT is defined, so the tool would need to know what directives are generated by the compiler itself as well as which ones are specified in the compile command rather than in a header file. 然后,只有在定义了WINNT时才定义USE_FEATURE_X ,因此该工具需要知道编译器本身生成的指令以及编译命令而不是头文件中指定的指令。

Like Timmermans, I'm not familiar with any tools for this. 像Timmermans一样,我对此并不熟悉任何工具。 But I have known programmers who wrote a Perl (or Python) script to try commenting out each include line one at a time and then compile each file. 但我知道编写Perl(或Python)脚本的程序员尝试一次注释掉每个include行,然后编译每个文件。


It appears that now Eric Raymond has a tool for this . 看来现在Eric Raymond 有了这个工具

Google's cpplint.py has an "include what you use" rule (among many others), but as far as I can tell, no "include only what you use." 谷歌的cpplint.py有一个“包含你使用的”规则(以及其他许多规则),但据我所知,没有“ 包括你使用的内容”。 Even so, it can be useful. 即便如此,它也很有用。

If you're interested in this topic in general, you might want to check out Lakos' Large Scale C++ Software Design . 如果您对这个主题感兴趣,可能需要查看Lakos的大规模C ++软件设计 It's a bit dated, but goes into lots of "physical design" issues like finding the absolute minimum of headers that need to be included. 这有点过时了,但是会遇到许多“物理设计”问题,例如找到需要包含的标题的绝对最小值。 I haven't really seen this sort of thing discussed anywhere else. 我还没有真正看到其他任何地方讨论过这种事情。

Give Include Manager a try. 试试Include Manager It integrates easily in Visual Studio and visualizes your include paths which helps you to find unnecessary stuff. 它可以在Visual Studio中轻松集成,并可视化您的包含路径,帮助您查找不必要的东西。 Internally it uses Graphviz but there are many more cool features. 在内部,它使用Graphviz,但还有许多很酷的功能。 And although it is a commercial product it has a very low price. 虽然它是商业产品,但价格非常低廉。

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

If your header files generally start with 如果您的头文件通常以

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

(as opposed to using #pragma once) you could change that to: (而不是使用#pragma一次)您可以将其更改为:

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

And since the compiler outputs the name of the cpp file being compiled, that would let you know at least which cpp file is causing the header to be brought in multiple times. 并且由于编译器输出正在编译的cpp文件的名称,这将使您至少知道哪个cpp文件导致多次引入标头。

PC-Lint can indeed do this. PC-Lint确实可以做到这一点。 One easy way to do this is to configure it to detect just unused include files and ignore all other issues. 一种简单的方法是将其配置为仅检测未使用的包含文件并忽略所有其他问题。 This is pretty straightforward - to enable just message 766 ("Header file not used in module"), just include the options -w0 +e766 on the command line. 这非常简单 - 只启用消息766(“模块中未使用的头文件”),只需在命令行中包含选项-w0 + e766即可。

The same approach can also be used with related messages such as 964 ("Header file not directly used in module") and 966 ("Indirectly included header file not used in module"). 相同的方法也可以与相关的消息一起使用,例如964(“未在模块中直接使用的头文件”)和966(“间接包含的未在模块中使用的头文件”)。

FWIW I wrote about this in more detail in a blog post last week at http://www.riverblade.co.uk/blog.php?archive=2008_09_01_archive.xml#3575027665614976318 . 我在上周的博客文章http://www.riverblade.co.uk/blog.php?archive=2008_09_01_archive.xml#3575027665614976318上更详细地写了这篇文章。

If you are looking to remove unnecessary #include files in order to decrease build times, your time and money might be better spent parallelizing your build process using cl.exe /MP , make -j , Xoreax IncrediBuild , distcc/ icecream , etc. 如果您希望删除不必要的#include文件以减少构建时间,那么使用cl.exe / MPmake -jXoreax IncrediBuild ,distcc / icecream等可以更好地花时间和金钱来构建进程。

Of course, if you already have a parallel build process and you're still trying to speed it up, then by all means clean up your #include directives and remove those unnecessary dependencies. 当然,如果您已经有一个并行构建过程并且仍在尝试加速它,那么一定要清理#include指令并删除那些不必要的依赖项。

Start with each include file, and ensure that each include file only includes what is necessary to compile itself. 从每个包含文件开始,并确保每个包含文件仅包含编译自身所需的内容。 Any include files that are then missing for the C++ files, can be added to the C++ files themselves. 然后,C ++文件中缺少的任何包含文件都可以添加到C ++文件本身。

For each include and source file, comment out each include file one at a time and see if it compiles. 对于每个包含文件和源文件,一次注释掉每个包含文件,看它是否编译。

It is also a good idea to sort the include files alphabetically, and where this is not possible, add a comment. 按字母顺序对包含文件进行排序也是一个好主意,如果不可能,请添加注释。

The latest Jetbrains IDE, CLion, automatically shows (in gray) the includes that are not used in the current file. 最新的Jetbrains IDE,CLion,自动显示(灰色)当前文件中未使用的包含。

It is also possible to have the list of all the unused includes (and also functions, methods, etc...) from the IDE. 也可以从IDE中获取所有未使用的包括(以及函数,方法等)的列表。

If you would work with Eclipse CDT you could try out http://includator.com to optimize your include structure. 如果您使用Eclipse CDT,可以尝试http://includator.com来优化您的包含结构。 However, Includator might not know enough about VC++'s predefined includes and setting up CDT to use VC++ with correct includes is not built into CDT yet. 但是,Includator可能对VC ++的预定义包含知识不够,并且设置CDT以使用正确包含的VC ++还没有内置到CDT中。

Adding one or both of the following #defines will exclude often unnecessary header files and may substantially improve compile times especially if the code that is not using Windows API functions. 添加以下一个或两个#defines将排除经常不必要的头文件,并且可能大大改善编译时间,尤其是在不使用Windows API函数的代码时。

#define WIN32_LEAN_AND_MEAN
#define VC_EXTRALEAN

See http://support.microsoft.com/kb/166474 请参见http://support.microsoft.com/kb/166474

If you aren't already, using a precompiled header to include everything that you're not going to change (platform headers, external SDK headers, or static already completed pieces of your project) will make a huge difference in build times. 如果您还没有,使用预编译的头文件包含您不会更改的所有内容(平台标头,外部SDK标头或项目的静态已完成部分)将在构建时间方面产生巨大差异。

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

Also, although it may be too late for your project, organizing your project into sections and not lumping all local headers to one big main header is a good practice, although it takes a little extra work. 此外,尽管对您的项目来说可能为时已晚,但将项目组织成各个部分而不是将所有本地标题集中到一个大的主标题是一个很好的做法,尽管需要一些额外的工作。

If there's a particular header that you think isn't needed anymore (say string.h), you can comment out that include then put this below all the includes: 如果你认为不再需要一个特定的标题(比如string.h),你可以注释掉include然后把它放在所有包含的下面:

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

Of course your interface headers might use a different #define convention to record their inclusion in CPP memory. 当然,您的接口标头可能使用不同的#define约定来记录它们包含在CPP内存中。 Or no convention, in which case this approach won't work. 或者没有惯例,在这种情况下这种方法不起作用。

Then rebuild. 然后重建。 There are three possibilities: 有三种可能性:

  • It builds ok. 它构建好了。 string.h wasn't compile-critical, and the include for it can be removed. string.h不是编译关键的,可以删除它的include。

  • The #error trips. #error之旅。 string.g was included indirectly somehow You still don't know if string.h is required. string.g以某种方式间接包含你仍然不知道是否需要string.h。 If it is required, you should directly #include it (see below). 如果需要,您应该直接#include它(见下文)。

  • You get some other compilation error. 你得到一些其他编译错误。 string.h was needed and isn't being included indirectly, so the include was correct to begin with. string.h是必需的,并没有间接包含,所以包含是正确的开始。

Note that depending on indirect inclusion when your .h or .c directly uses another .h is almost certainly a bug: you are in effect promising that your code will only require that header as long as some other header you're using requires it, which probably isn't what you meant. 请注意,当您的.h或.c直接使用另一个.h时,取决于间接包含。几乎肯定是一个错误:您实际上承诺,只要您使用的其他标头需要它,您的代码将只需要该标头,这可能不是你的意思。

The caveats mentioned in other answers about headers that modify behavior rather that declaring things which cause build failures apply here as well. 在其他答案中提到的关于修改行为的标题而不是声明导致构建失败的事情的注释也适用于此处。

Maybe a little late, but I once found a WebKit perl script that did just what you wanted. 也许有点晚了,但我曾经发现一个WebKit perl脚本可以完成你想要的。 It'll need some adapting I believe (I'm not well versed in perl), but it should do the trick: 它需要一些适应我相信(我不熟悉perl),但它应该做的伎俩:

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

(this is an old branch because trunk doesn't have the file anymore) (这是一个旧分支,因为trunk不再有文件)

Some of the existing answers state that it's hard. 一些现有的答案表明它很难。 That's indeed true, because you need a full compiler to detect the cases in which a forward declaration would be appropriate. 确实如此,因为您需要一个完整的编译器来检测前向声明适合的情况。 You cant parse C++ without knowing what the symbols mean; 你不能解析C ++而不知道符号是什么意思; the grammar is simply too ambiguous for that. 语法对此来说太模糊了。 You must know whether a certain name names a class (could be forward-declared) or a variable (can't). 您必须知道某个名称是否命名一个类(可以是前向声明的)还是一个变量(不能)。 Also, you need to be namespace-aware. 此外,您需要知道名称空间。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM