头文件包含静态分析工具



一位同事最近向我透露,我们的一个源文件在编译期间包含超过3,400个头文件。我们在一个构建中编译了超过1000个翻译单元,导致了巨大的性能损失,因为头文件肯定没有全部使用。

是否有任何静态分析工具能够阐明这样一个森林中的树木,特别是给我们决定我们应该努力淘汰哪些树木的能力?

在这里找到了一些关于包含头文件的成本的有趣信息(以及优化其包含的包含保护的类型),这些信息起源于这个问题。

gcc -w -H <file>的输出可能是有用的(如果您解析它并放入一些计数),-w在那里抑制所有警告,这可能难以处理。

来自gcc文档:

- h

打印所使用的每个头文件的名称,以及其他正常活动。每个名称都被缩进,以显示其深度即#include堆叠。预编译的头文件也会打印出来,即使被认定无效;无效的预编译头文件文件用...x打印,有效文件用...!打印。

输出如下所示:

. /usr/include/unistd.h
.. /usr/include/features.h
... /usr/include/bits/predefs.h
... /usr/include/sys/cdefs.h
.... /usr/include/bits/wordsize.h
... /usr/include/gnu/stubs.h
.... /usr/include/bits/wordsize.h
.... /usr/include/gnu/stubs-64.h
.. /usr/include/bits/posix_opt.h
.. /usr/include/bits/environments.h
... /usr/include/bits/wordsize.h
.. /usr/include/bits/types.h
... /usr/include/bits/wordsize.h
... /usr/include/bits/typesizes.h
.. /usr/lib/x86_64-linux-gnu/gcc/x86_64-linux-gnu/4.5.2/include/stddef.h
.. /usr/include/bits/confname.h
.. /usr/include/getopt.h
. /usr/include/stdio.h
.. /usr/lib/x86_64-linux-gnu/gcc/x86_64-linux-gnu/4.5.2/include/stddef.h
.. /usr/include/libio.h
... /usr/include/_G_config.h
.... /usr/lib/x86_64-linux-gnu/gcc/x86_64-linux-gnu/4.5.2/include/stddef.h
.... /usr/include/wchar.h
... /usr/lib/x86_64-linux-gnu/gcc/x86_64-linux-gnu/4.5.2/include/stdarg.h
.. /usr/include/bits/stdio_lim.h
.. /usr/include/bits/sys_errlist.h
Multiple include guards may be useful for:
/usr/include/bits/confname.h
/usr/include/bits/environments.h
/usr/include/bits/predefs.h
/usr/include/bits/stdio_lim.h
/usr/include/bits/sys_errlist.h
/usr/include/bits/typesizes.h
/usr/include/gnu/stubs-64.h
/usr/include/gnu/stubs.h
/usr/include/wchar.h

如果您使用的是gcc/g++, -M-MM选项将输出一行您所查找的信息。(前者将包括系统头文件,而后者不包括。还有其他的变体;)

$ gcc -M -c foo.c
foo.o: foo.c /usr/include/stdint.h /usr/include/features.h 
  /usr/include/sys/cdefs.h /usr/include/bits/wordsize.h 
  /usr/include/gnu/stubs.h /usr/include/gnu/stubs-64.h 
  /usr/include/bits/wchar.h

您需要在开始时删除foo.o: foo.c,但其余部分是文件所依赖的所有头的列表,因此编写一个脚本来收集这些并总结它们并不太难。

当然,这个建议只在Unix上有用,而且只有在没有其他人有更好的主意的情况下才有用。: -)

几件事——

  • 使用"preprocess only"查看你的预处理器输出。

  • 使用预编译头文件

  • gcc有-verbose和——trace选项,它们也显示完整的包含树,MSVC有/showIncludes选项,在高级c++属性页

另外,在Visual Studio中显示c++文件的#include层次

John Lakos的"Large Scale c++ Software Design"有一些工具可以提取源文件之间的编译时依赖关系。

不幸的是,他们在Addison-Wesley网站上的存储库已经消失了(以及AW的网站本身),但我在这里找到了一个tarball:http://prdownloads.sourceforge.net/introspector/LSC-rpkg-0.1.tgz?download

我在几次工作前发现它很有用,而且它的优点是免费的。

顺便说一句,如果你还没有读过Lakos的书,听起来你的项目会受益。(当前版本有点过时了,但我听说Lakos在2012年有另一本书出版)

GCC有一个-M标志,它将输出给定源文件的依赖项列表。您可以使用这些信息来找出哪些文件具有最多的依赖项,哪些文件最依赖,等等。

查看手册页了解更多信息。-M有几个变种

我个人不知道是否有一个工具会说"删除这个文件"。这真的是一个复杂的问题,取决于很多东西。查看包含语句的树肯定会让您发疯....那会让我发疯,也会毁了我的眼睛。有更好的方法来减少编译时间。

    取消内联的类方法。
  1. 在定义它们之后,重新检查你的include语句并尝试删除它们。通常有帮助的是删除它们,然后重新开始。
  2. 倾向于尽可能使用前向声明。如果你在头文件中取消内联方法,你可以做很多。
  3. 将大的头文件分解成较小的文件。如果一个类在一个文件中被频繁使用,那么把它单独放在一个头文件中。
  4. 1000平移单位实际上不是很多。我们有1 -2万个。:)
  5. 如果你的编译时间仍然太长,得到Incredibuild。

我听说有一些工具可以做到这一点,但我没有使用它们。

我创建了一些工具https://sourceforge.net/p/headerfinder可能这是有用的。不幸的是,它是"自制"工具,有以下问题,

  • 在Vb中开发。净
  • 需要编译的源代码
  • 非常慢,占用内存。

GCC有一个可以保存中间文件的标志(-save-temps)。这包括.ii文件,它们是预处理器的结果(因此在编译之前)。您可以编写一个脚本来解析它,并确定所包含内容的权重/成本/大小,以及依赖树。

我写了一个Python脚本来做这件事(公开在这里:https://gitlab.com/p_b_omta/gcc-include-analyzer)。

最新更新