我应该如何检测大型 C++ 项目中不必要的#include 文件?
Posted
技术标签:
【中文标题】我应该如何检测大型 C++ 项目中不必要的#include 文件?【英文标题】:How should I detect unnecessary #include files in a large C++ project? 【发布时间】:2010-09-09 14:44:30 【问题描述】:我正在 Visual Studio 2008 中处理一个大型 C++ 项目,并且有很多文件包含不必要的 #include
指令。有时#include
s 只是工件,删除它们后一切都可以正常编译,而在其他情况下,可以向前声明类并且可以将#include 移动到.cpp
文件中。有什么好的工具可以检测这两种情况吗?
【问题讨论】:
【参考方案1】:有一个新的基于 Clang 的工具,include-what-you-use,旨在做到这一点。
【讨论】:
【参考方案2】:虽然它不会显示不需要的包含文件,但 Visual Studio 有一个设置 /showIncludes
(右键单击 .cpp
文件,Properties->C/C++->Advanced
),它将在编译时输出所有包含文件的树。这有助于识别不需要包含的文件。
您还可以查看 pimpl idiom 以减少头文件依赖项,从而更轻松地查看可以删除的内容。
【讨论】:
/showincludes 很棒。没有它,手动执行此操作令人生畏。 这个输出在哪里?此外,为了让未来的读者更快地阅读,您需要在解决方案资源管理器中右键单击该文件。【参考方案3】:最新的 Jetbrains IDE CLion 会自动显示(灰色)当前文件中未使用的包含。
还可以从 IDE 中获取所有未使用的包含(以及函数、方法等)的列表。
【讨论】:
【参考方案4】:如果您认为不再需要某个特定标题(例如 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 几乎可以肯定是一个错误:您实际上是在承诺您的 只要您正在使用的其他标头,代码就只需要该标头 需要它,这可能不是你的意思。
其他答案中提到的关于修改行为的标头的警告 而声明导致构建失败的东西也适用于此。
【讨论】:
【参考方案5】:和 Timmermans 一样,我不熟悉任何用于此目的的工具。但我认识的程序员写了一个 Perl(或 Python)脚本来尝试一次注释掉每个包含行,然后编译每个文件。
现在看来,Eric Raymond has a tool for this。
Google 的 cpplint.py 有一个“包括您使用的内容”规则(以及许多其他规则),但据我所知,没有“包括仅您使用的内容”。即便如此,它还是很有用的。
【讨论】:
当我读到这篇文章时,我不得不笑。就在上个月,我的老板在我们的一个项目上做了这件事。减少的标题包括几个因素。 codewarrior 在 mac 上曾经有一个内置的脚本来执行此操作,注释掉,编译,错误取消注释,继续到 #includes 的末尾。它仅适用于文件顶部的#includes,但这通常是它们所在的位置。它并不完美,但它确实使事情保持合理。 deheader 不幸地建议程序员不要从 foo.cpp 中包含 foo.hpp...。【参考方案6】:如果您使用 Eclipse CDT,您可以尝试使用http://includator.com 来优化您的包含结构。但是,Includator 可能对 VC++ 的预定义包含不够了解,并且设置 CDT 以使用具有正确包含的 VC++ 尚未内置到 CDT 中。
【讨论】:
【参考方案7】:也许有点晚了,但我曾经发现一个 WebKit perl 脚本可以满足您的需求。我相信它需要一些调整(我不太精通 perl),但它应该可以解决问题:
http://trac.webkit.org/browser/branches/old/safari-3-2-branch/WebKitTools/Scripts/find-extra-includes
(这是一个旧分支,因为主干不再有文件)
【讨论】:
【参考方案8】:您可以使用C/C++ Include File Dependencies Watcher 构建包含图,并直观地查找不需要的包含。
【讨论】:
【参考方案9】:试试Include Manager。它可以轻松集成到 Visual Studio 中,并可视化您的包含路径,从而帮助您找到不必要的东西。 它在内部使用 Graphviz,但还有更多很酷的功能。虽然它是商业产品,但价格非常低。
【讨论】:
【参考方案10】:如果您对该主题感兴趣,您可能需要查看 Lakos 的 Large Scale C++ Software Design。它有点过时了,但涉及到许多“物理设计”问题,比如找到需要包含的绝对最小的标题。我还没有在其他任何地方真正看到过这种讨论。
【讨论】:
【参考方案11】:PC-Lint 确实可以做到这一点。一种简单的方法是将其配置为仅检测未使用的包含文件并忽略所有其他问题。这非常简单 - 只启用消息 766(“模块中未使用的头文件”),只需在命令行中包含选项 -w0 +e766。
同样的方法也可以用于相关消息,例如 964(“模块中未直接使用的头文件”)和 966(“模块中未使用的间接包含的头文件”)。
FWIW 我上周在http://www.riverblade.co.uk/blog.php?archive=2008_09_01_archive.xml#3575027665614976318 的一篇博文中更详细地介绍了这一点。
【讨论】:
【参考方案12】:如果你的头文件一般以
开头#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 文件导致头被多次引入。
【讨论】:
我认为多次包含标题很好。最好包含您使用的内容,而不是依赖您的包含文件来执行此操作。我认为 OP 想要的是找到没有实际使用的#includes。 IMO 主动做错了事。如果没有它们就无法工作,标题应该包含其他标题。当你有A.h
和B.h
都依赖于C.h
并且你包括A.h
和B.h
时,因为你需要两者,你将 包括C.h
两次,但是没关系,因为编译器会第二次跳过它,如果你不这样做,你必须记住总是在 A.h
或 B.h
之前包含 C.h
,最终导致更多无用的包含。
内容准确,这是查找包含多次的标题的好方法。然而,最初的问题并没有被这个回答,我无法想象这什么时候会是个好主意。 Cpp 文件应包含它们所依赖的所有标头,即使标头在其他位置之前包含。您不希望您的项目是特定于编译顺序的,或者假设不同的标头将包含您需要的标头。【参考方案13】:
从每个包含文件开始,并确保每个包含文件只包含编译自身所需的内容。 C++ 文件中缺少的任何包含文件都可以添加到 C++ 文件本身中。
对于每个包含和源文件,一次注释掉每个包含文件,看看它是否可以编译。
按字母顺序对包含文件进行排序也是一个好主意,如果无法做到这一点,请添加注释。
【讨论】:
如果涉及到大量的实现文件,我不确定这条评论是否实用。【参考方案14】:如果您还没有使用预编译头文件来包含您不会更改的所有内容(平台头文件、外部 SDK 头文件或项目中已完成的静态部分),那么构建时间会产生巨大差异.
http://msdn.microsoft.com/en-us/library/szfdksca(VS.71).aspx
此外,尽管对您的项目来说可能为时已晚,但将项目组织成多个部分并且不将所有本地标题集中到一个大的主标题中是一种很好的做法,尽管这需要一些额外的工作。
【讨论】:
对预编译头文件的很好解释:cygnus-software.com/papers/precompiledheaders.html(不确定在最新版本的 VisualStudio 中自动生成预编译头文件是否损坏,但值得检查。)【参考方案15】:!!免责声明!!我从事商业静态分析工具(不是 PC Lint)。 !!免责声明!!
简单的非解析方法存在几个问题:
1) 重载集:
重载函数的声明可能来自不同的文件。可能是删除一个头文件会导致选择不同的重载而不是编译错误!结果将是语义上的静默变化,之后可能很难追踪。
2) 模板特化:
与重载示例类似,如果您有模板的部分或显式特化,您希望在使用模板时它们全部可见。可能是主模板的特化位于不同的头文件中。使用特化删除标头不会导致编译错误,但如果选择了特化,则可能导致未定义的行为。 (见:Visibility of template specialization of C++ function)
正如“msalters”所指出的,对代码进行全面分析还可以分析类的使用情况。通过检查如何通过特定的文件路径使用类,可以完全删除类的定义(以及因此它的所有依赖项),或者至少将其移动到更接近包含中主要源的级别树。
【讨论】:
@XanderTulip:如果不进行推销,就很难对此做出回应——所以我提前道歉。恕我直言,您必须考虑的是,一名优秀的工程师需要多长时间才能在任何合理规模的项目中找到这样的事情(以及许多其他语言/控制流错误)。随着软件的变化,同样的任务需要一次又一次地重复。因此,当您计算出节省的时间时,工具的成本可能并不显着。【参考方案16】:一些现有的答案表明这很难。确实如此,因为您需要一个完整的编译器来检测适合前向声明的情况。如果不知道符号的含义,就无法解析 C++;语法太模棱两可了。您必须知道某个名称是命名一个类(可以向前声明)还是一个变量(不能)。此外,您需要了解命名空间。
【讨论】:
你可以说“决定哪些#includes 是必要的就等于解决停机问题。祝你好运:)”当然,你可以使用启发式,但我不知道任何免费软件这样做。【参考方案17】:如果您希望删除不必要的 #include
文件以减少构建时间,您最好将时间和金钱花在使用 cl.exe /MP、make -j、Xoreax IncrediBuild、distcc/@987654324 并行构建过程上@等
当然,如果您已经有一个并行构建过程并且您仍在尝试加快它,那么请务必清理您的 #include
指令并删除那些不必要的依赖项。
【讨论】:
【参考方案18】:添加以下一项或两项#defines 将排除通常不必要的头文件和 可能会大大改善 编译时间,尤其是在不使用 Windows API 函数的代码时。
#define WIN32_LEAN_AND_MEAN
#define VC_EXTRALEAN
见http://support.microsoft.com/kb/166474
【讨论】:
两者都不需要 - VC_EXTRALEAN 定义了 WIN32_LEAN_AND_MEAN【参考方案19】:PC Lint 可以很好地解决这个问题,它还可以为您找到各种其他愚蠢的问题。它具有可用于在 Visual Studio 中创建外部工具的命令行选项,但我发现 Visual Lint 插件更易于使用。甚至 Visual Lint 的免费版本也有帮助。但是给 PC-Lint 一个机会。配置它以便它不会给你太多警告需要一些时间,但你会惊讶于它的出现。
【讨论】:
可以在riverblade.co.uk/…找到一些关于如何使用 pc-lint 的说明【参考方案20】:我不知道有这样的工具,之前也想过写一个,结果发现这是一个很难解决的问题。
假设你的源文件包含 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
时才被定义,因此该工具需要知道编译器本身生成了哪些指令,以及哪些指令是在编译命令中而不是在头文件中指定的。
【讨论】:
以上是关于我应该如何检测大型 C++ 项目中不必要的#include 文件?的主要内容,如果未能解决你的问题,请参考以下文章
如何防止 Visual Studio 2010 在 C++ 中向我的项目添加大型 SQL 文件?
在 Visual C++ (Windows) 中检测内存泄漏