我应该如何检测大型 C++ 项目中不必要的 #include 文件?
-
09-06-2019 - |
题
我正在 Visual Studio 2008 中处理一个大型 C++ 项目,并且有很多不必要的文件 #include
指令。有时, #include
s 只是工件,删除它们后一切都会正常编译,在其他情况下,可以向前声明类,并且可以将 #include 移动到 .cpp
文件。有没有什么好的工具可以检测这两种情况?
解决方案
虽然它不会显示不需要的包含文件,但 Visual Studio 有一个设置 /showIncludes
(右键单击 .cpp
文件, Properties->C/C++->Advanced
)将在编译时输出所有包含文件的树。这可以帮助识别不需要包含的文件。
您还可以查看 pimpl 惯用语,让您摆脱更少的头文件依赖项,从而更容易地看到可以删除的缺陷。
其他提示
有一个新的基于 Clang 的工具, 包括您使用的内容, ,旨在做到这一点。
!!免责声明!!我正在开发一个商业静态分析工具(不是 PC Lint)。!!免责声明!!
简单的非解析方法存在几个问题:
1) 超载组:
重载函数可能具有来自不同文件的声明。删除一个头文件可能会导致选择不同的重载,而不是编译错误!结果将是语义上的无声变化,之后可能很难追踪。
2) 模板专业化:
与重载示例类似,如果您对模板有部分或显式特化,您希望在使用模板时它们全部可见。主模板的特化可能位于不同的头文件中。删除具有专业化的标头不会导致编译错误,但如果选择了该专业化,则可能会导致未定义的行为。(看: C++ 函数模板特化的可见性)
正如“msalters”所指出的,对代码进行全面分析还可以分析类的使用情况。通过检查特定文件路径如何使用类,可以完全删除该类的定义(及其所有依赖项)或至少移动到更接近包含中的主要源代码的级别树。
我不知道有这样的工具,我过去曾想过写一个,但事实证明这是一个很难解决的问题。
假设你的源文件包含 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
已定义,因此该工具需要知道编译器本身生成哪些指令以及哪些指令是在编译命令中而不是在头文件中指定的。
和蒂默曼斯一样,我不熟悉任何这方面的工具。但我认识一些程序员,他们编写了 Perl(或 Python)脚本,尝试一次注释掉每个包含行,然后编译每个文件。
看来现在埃里克·雷蒙德 有一个工具可以做到这一点.
谷歌的 cplint.py 有一个“包括你使用的”规则(以及其他许多规则),但据我所知,没有“包括 仅有的 你用什么。”即便如此,它仍然很有用。
如果您对这个主题总体感兴趣,您可能需要查看 Lakos 的 大规模C++软件设计. 。它有点过时,但涉及许多“物理设计”问题,例如找到需要包含的标头的绝对最小值。我还没有在其他地方真正看到过此类问题的讨论。
给 包括经理 尝试一下。它可以轻松集成到 Visual Studio 中,并可视化您的包含路径,帮助您找到不必要的东西。它在内部使用 Graphviz,但还有许多更酷的功能。尽管它是商业产品,但价格非常低。
您可以使用构建包含图 C/C++ 包含文件依赖关系观察器, ,并直观地找到不需要的包含。
如果你的头文件通常以
#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 文件导致标头被多次引入。
PC-Lint 确实可以做到这一点。一种简单的方法是将其配置为仅检测未使用的包含文件并忽略所有其他问题。这非常简单 - 要仅启用消息 766(“模块中未使用头文件”),只需在命令行上包含选项 -w0 +e766 即可。
同样的方法也可以用于相关消息,例如 964(“模块中未直接使用头文件”)和 966(“模块中未使用间接包含的头文件”)。
FWIW 我上周在博客文章中更详细地写了这一点 http://www.riverblade.co.uk/blog.php?archive=2008_09_01_archive.xml#3575027665614976318.
如果您想删除不必要的 #include
为了减少构建时间,您的时间和金钱最好花在并行构建过程上 cl.exe /MP, 使-j, Xoreax IncrediBuild, 区/冰淇淋, , ETC。
当然,如果您已经有了并行构建过程并且仍在尝试加快速度,那么请务必清理您的 #include
指令并删除那些不必要的依赖项。
从每个包含文件开始,并确保每个包含文件仅包含编译自身所需的内容。C++ 文件中缺少的任何包含文件都可以添加到 C++ 文件本身中。
对于每个包含文件和源文件,一次注释掉每个包含文件并查看它是否可以编译。
按字母顺序对包含文件进行排序也是一个好主意,如果不可能,请添加注释。
添加以下#Defines中的一个或两个将排除通常不必要的标头文件,并且可能会大大改善编译时间,尤其是在不使用Windows API函数的代码时。
#define WIN32_LEAN_AND_MEAN
#define VC_EXTRALEAN
如果您还没有这样做,那么使用预编译标头来包含您不会更改的所有内容(平台标头、外部 SDK 标头或项目中已完成的静态部分)将在构建时间上产生巨大差异。
http://msdn.microsoft.com/en-us/library/szfdksca(VS.71).aspx
此外,尽管这对于您的项目来说可能为时已晚,但将项目组织为多个部分,而不是将所有本地标题集中到一个大的主标题中是一种很好的做法,尽管这需要一些额外的工作。
如果您想使用 Eclipse CDT,您可以尝试一下 http://includator.com 优化您的包含结构。但是,Includator 可能对 VC++ 的预定义包含了解不够,并且设置 CDT 以使用 VC++ 并正确包含尚未内置到 CDT 中。
最新的 Jetbrains IDE CLion 会自动显示(以灰色显示)当前文件中未使用的包含内容。
还可以从 IDE 获取所有未使用的包含(以及函数、方法等)的列表。
一些现有的答案表明这很难。这确实是事实,因为您需要一个完整的编译器来检测适合前向声明的情况。如果不知道符号的含义,就无法解析 C++;语法对此来说太模糊了。您必须知道某个名称是命名类(可以前向声明)还是变量(不能)。此外,您还需要了解名称空间。
也许有点晚了,但我曾经发现一个 WebKit perl 脚本可以满足您的需求。我相信它需要一些调整(我不太精通 perl),但它应该可以解决问题:
(这是一个旧分支,因为主干不再有该文件)
如果您认为不再需要一个特定的标题(例如String.h),则可以评论其中包含,然后将其放在所有内容下:
#ifdef _STRING_H_
# error string.h is included indirectly
#endif
当然,您的接口标题可能会使用其他#Define惯例记录其包含在CPP内存中。或没有惯例,在这种情况下,这种方法将行不通。
然后重建。有以下三种可能:
它构建正常。string.h并不是批判性的,可以将其删除。
#error 跳闸。string.g是间接包含的,您仍然不知道是否需要string.h。如果需要,则应直接#CINGUDE(请参见下文)。
您会遇到其他一些编译错误。string.h是需要的,并且不是间接包含的,因此包括正确的开始是正确的。
请注意,根据您的.h或.c直接使用另一个.h,几乎可以肯定是一个错误:实际上,您承诺只要您使用的其他标头需要它,您的代码只需要该标头,这可能不是您的意思。
其他答案中提到的有关修改行为的标题中提到的警告,而不是在此处宣布引起构建失败的事物。