4

清理导致编译时间极慢(Linux / Unix)的“标题意大利面条”的任何推荐做法?

GCC 有没有相当于“#pragma once”的东西?
(发现与此有关的相互矛盾的消息)

谢谢。

4

11 回答 11

8

假设您熟悉“包含守卫”(标头开头的#ifdef..),另一种加快构建时间的方法是使用外部包含守卫。它在“大规模 C++ 软件设计”中进行了讨论。这个想法是经典的包含保护,不像#pragma once,不要让你从第二次开始忽略标题所需的预处理器解析(即它仍然必须解析并查找包含保护的开始和结束。用外部包含保护您将#ifdef 放在#include 行本身周围。

所以它看起来像这样:

#ifndef MY_HEADER
#include "myheader.h"
#endif

当然在 H 文件中你有经典的包含保护

#ifndef MY_HEADER
#define MY_HEADER

// content of header

#endif

这样,myheader.h 文件甚至不会被预处理器打开/解析,它可以在大型项目中为您节省大量时间,尤其是当头文件位于共享的远程位置时,就像它们有时那样。

再一次,这一切都在那本书里。hth

于 2008-09-21T09:15:27.483 回答
6

如果你想做一个完整的清理并有时间去做,那么最好的解决方案是删除所有文件中的所有#includes(除了明显的,例如abc.cpp中的abc.h),然后编译项目。添加必要的前向声明或标题来修复第一个错误,然后重复直到你干净地完成。

这并不能解决可能导致包含问题的潜在问题,但它确实确保了唯一的包含是必需的。

于 2008-09-21T10:00:56.547 回答
4

我读过 GCC 认为#pragma once已弃用,尽管甚至#pragma once只能做这么多来加快速度。

要尝试解开#include意大利面,您可以查看doxygen。它应该能够生成包含标题的图表,这可能会给您简化事情的优势。我无法立即回忆起详细信息,但图形功能可能需要您安装GraphViz并告诉 doxygen 它可以找到 GraphViz 的 dotty.exe 的路径。

如果编译时间是您的主要关注点,您可能会考虑的另一种方法是设置Precompiled Headers

于 2008-09-21T07:39:47.353 回答
3

理查德有点正确(为什么他的解决方案被记录下来?)。

无论如何,所有 C/C++ 头文件都应该使用内部包含保护。

这说,要么:

1 - 您的遗留代码不再真正维护,您应该使用预编译的头文件(这是一个 hack,但是嘿......您需要加快编译速度,而不是重构未维护的代码)

2 - 您的遗留代码仍然存在。然后,您可以使用预编译的头文件和/或防护/外部防护作为临时解决方案,但最后,您需要删除所有包含,一次一个 .C 或 .CPP,并编译每个 . C 或 .CPP 文件一次一个,在必要时使用前向声明或包含来更正它们的包含(或者甚至将大型包含分成较小的包含以确保每个 .C 或 .CPP 文件将只获得它需要的标题)。无论如何,测试和删除过时的包含是项目维护的一部分,所以......

我自己对预编译头文件的体验并不是很好,因为一半时间,编译器找不到我定义的符号,所以我尝试了完整的“清理/重建”,以确保它不是预编译头文件那已经过时了。所以我的猜测是将它用于你甚至不会接触的外部库(比如 STL、C API 头文件、Boost 等等)。不过,我自己的经验是使用 Visual C++ 6,所以我猜(希望?)他们现在做对了。

现在,最后一件事:标题应该始终是自给自足的。这意味着如果标题的包含取决于包含顺序,那么您就有问题了。例如,如果你可以写:

#include "AAA.hpp"
#include "BBB.hpp"

但不是:

#include "BBB.hpp"
#include "AAA.hpp"

因为 BBB 依赖于 AAA,所以你所拥有的只是一个你从未在代码中承认的依赖。不通过定义来确认它只会让你的编译成为一场噩梦。BBB 也应该包含 AAA(即使它可能会慢一些:最后,前向声明无论如何都会清除无用的包含,因此您应该有一个更快的编译计时器)。

于 2008-09-21T10:32:20.963 回答
3

前几天我读到了一个减少头文件依赖的巧妙技巧:编写一个脚本

  • 查找所有#include 语句
  • 一次删除一条语句并重新编译
  • 如果编译失败,请重新添加 include 语句

最后,希望您的代码中包含最少的必需包含项。您可以编写一个类似的脚本,重新排列包含以找出它们是否自给自足,或者要求在它们之前包含其他标头(首先包含标头,看看编译是否失败,报告它)。这应该有助于清理你的代码。

还有一些注意事项:

  • 现代编译器(其中包括 gcc)识别标头保护,并以与 pragma once 相同的方式进行优化,只打开文件一次。
  • 当同一个文件在文件系统中具有不同的名称(即使用软链接)时,pragma once 可能会出现问题

  • gcc 支持 #pragma 一次,但称其为“过时”
  • 并非所有编译器都支持 pragma once,也不是 C 标准的一部分

  • 不仅编译器可能有问题。像 Incredibuild 这样的工具也有 #pragma once 的问题
于 2008-10-22T15:07:58.363 回答
2

使用其中一种或多种来加快构建时间

  1. 使用预编译头文件
  2. 使用缓存机制(例如 scons)
  3. 使用分布式构建系统 ( distcc, Incredibuild($) )
于 2008-09-21T07:53:12.920 回答
0

在标头中:仅当您不能使用前向声明时才包含标头,但始终#include 您需要的任何文件(包含依赖项是邪恶的!)。

于 2008-09-21T07:07:50.177 回答
0

如另一个答案中所述,您绝对应该尽可能使用前向声明。据我所知,GCC 没有任何与 #pragma once 等价的东西,这就是为什么我坚持包含守卫的旧时尚风格。

于 2008-09-21T07:17:24.130 回答
0

感谢您的回复,但问题是关于现有代码,其中包括严格的“包含顺序”等。问题是是否有任何工具/脚本来澄清实际发生的事情。

标头保护不是解决方案,因为它们不会阻止编译器一次又一次地读取整个文件并且......

于 2008-09-21T07:25:58.167 回答
0

PC-Lint将大大有助于清理意大利面条头。它还将为您解决其他问题,例如未初始化的变量看不见等。

于 2008-09-21T10:04:48.003 回答
0

正如 onebyone.livejournal.com 在回复您的问题时评论的那样,一些编译器支持包括保护优化,我链接的页面定义如下:

包含保护优化是当编译器识别出上述内部包含保护习语并采取措施避免多次打开文件时。编译器可以查看包含文件,去掉注释和空格,并确定整个文件是否在包含保护范围内。如果是,它将文件名和包含保护条件存储在映射中。下次要求编译器包含文件时,它可以检查包含保护条件并决定是跳过文件还是#include 它而不需要打开文件。

再说一次,您已经回答了外部包含守卫不是您问题的答案。对于必须以特定顺序包含的头文件,我建议如下:

  • 每个.cor.cpp文件应该首先#include是相应的.h文件,其余的#include指令应该按字母顺序排序。当这破坏了头文件之间未说明的依赖关系时,通常会出现构建错误。
  • #define如果您有一个头文件为基本类型或大多数代码使用的全局指令定义全局 typedef ,则每个.h文件都应该#include首先是该文件,其余的#include指令应该按字母顺序排序。
  • 当这些更改导致编译错误时,您通常必须以#include.
  • 当这些更改不会导致编译错误时,它们可能会导致行为更改。希望您有某种测试套件可用于验证应用程序的功能。

听起来问题的一部分可能是增量构建比它们应该的要慢得多。正如其他人所指出的,这种情况可以通过前向声明或分布式构建系统来改善。

于 2008-09-21T15:48:06.603 回答