头文件包含静态分析工具?

27
一位同事最近向我透露,我们的一个单一源文件在编译时包括超过3,400个头文件。我们有超过1,000个翻译单元在构建中被编译,这导致了巨大的性能损失,而这些头文件肯定不都被使用。
是否有任何静态分析工具能够揭示这样一个森林中的树木,特别是给予我们决定应该削减哪些树木的能力?
更新:在这里找到了一些关于包含头文件的成本(以及优化其包含的类型)的有趣信息,源自这个问题

什么平台?gcc有一些选项可以帮助解决这个问题(如果没有更好的建议)。 - Nemo
可能是 https://dev59.com/-3VD5IYBdhLWcg3wO5ED 的重复问题。 - jfritz42
@fbrereto:(几年后...)我正在为C语言开发一种类型推断,其中一个预期的用途是删除所有的#include,让引擎仅推断实际使用的类型,并且仍然能够以相同的精度获得分析结果。我正在寻找真实世界的案例来撰写论文。如果您想讨论这个问题,请直接联系我。该引擎有一个在线界面:http://cuda.dcc.ufmg.br/psyche-c/ - Leandro T. C. Melo
8个回答

26

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

有没有类似的东西适用于Devenv/MSVS? - andreas
@user375251 我不知道,不要使用微软的工具链做任何事情,去查看MSDN。 - Spudd86
@Spudd86,我能否获取项目中每个文件的包含列表,而无需手动键入每个文件名和-H选项? - Alecs
@Alecs 也许你可以调整一下你的 Makefile,这样就可以在加上那个标志的情况下运行完整的构建...或者添加第二个命令到你的构建规则中,用那个标志运行 gcc 并将其输出重定向到一个文件或其他地方。 - Spudd86

3
如果你正在使用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,并且只有在没有其他更好的想法时才有用。 :-)

1
并不是很有用,无法找出所有包含文件的来源。 - Spudd86

3
一些事情:
  • 使用"仅预处理"来查看您的预处理器输出。gcc -E选项,其他编译器也有此功能。

  • 使用预编译头文件。

  • gcc具有-verbose和--trace选项,这些选项还会显示完整的包含树,MSVC在高级C++属性页下找到/showIncludes选项。

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


2
"Large Scale C++ Software Design"一书中,作者John Lakos提供了一些工具,可以提取源文件之间的编译时依赖关系。
不幸的是,Addison-Wesley网站上的代码库已经消失(包括AW网站本身),但我在这里找到了一个tarball: http://prdownloads.sourceforge.net/introspector/LSC-rpkg-0.1.tgz?download 在我几份工作中,我发现它非常有用,并且它是免费的。
顺便说一下,如果您还没有阅读过Lakos的书,那么您的项目可能会受益。 (当前版本有点过时,但我听说Lakos将在2012年推出另一本新书。)

1

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

请查看手册页以获取更多信息。有几个变体的-M


1

就我个人而言,我不知道是否有一种工具可以说“删除这个文件”。这是一个非常复杂的问题,取决于很多因素。查看包含语句树肯定会让你发疯...它也会让我发疯,还会毁了我的眼睛。有更好的方法来减少编译时间。

  1. 取消内联类方法。
  2. 在取消内联方法后,重新检查您的包含语句并尝试删除它们。通常有帮助的是将它们删除,然后重新开始。
  3. 尽可能使用前向声明。如果您在头文件中取消内联方法,则可以经常这样做。
  4. 将大型头文件拆分为较小的文件。如果文件中的类比大多数文件更常用,则将其放在单独的头文件中。
  5. 1000个翻译单元实际上并不是很多。我们有10-20万个。 :)
  6. 如果编译时间仍然太长,请使用Incredibuild。

0

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

我创建了一些工具https://sourceforge.net/p/headerfinder,也许这很有用。不幸的是,这是一个“自制”的工具,存在以下问题:

  • 使用Vb.Net开发
  • 需要编译源代码
  • 非常缓慢且占用内存。
  • 没有可用的帮助文档。

0
GCC有一个标志(-save-temps),可以保存中间文件。这包括预处理器的结果(即编译之前)的.ii文件。您可以编写一个脚本来解析它,并确定所包含内容的重量/成本/大小,以及依赖关系树。
我编写了一个Python脚本来完成这个任务(公开可用于https://gitlab.com/p_b_omta/gcc-include-analyzer)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接