清理遗留代码“header spaghetti”

发布于 2024-07-05 08:28:23 字数 132 浏览 5 评论 0原文

任何清理“标题意大利面”的推荐做法,这会导致极其严重的后果 编译时间慢(Linux/Unix)?

GCC 中是否有相当于“#pragma Once”的东西?
(发现与此相关的冲突消息)

谢谢。

Any recommended practices for cleaning up "header spaghetti" which is causing extremely
slow compilation times (Linux/Unix)?

Is there any equvalent to "#pragma once" with GCC?
(found conflicting messages regarding this)

Thanks.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(11

南街女流氓 2024-07-12 08:28:23

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

因此它看起来像这样:

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

当然,在 H 文件中,您有经典的包含防护,

#ifndef MY_HEADER
#define MY_HEADER

// content of header

#endif

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

,这一切都在那本书中。

Assuming you're familiar with "include guards" (#ifdef at the begining of the header..), an additional way of speeding up build time is by using external include guards.
It was discussed in "Large Scale C++ Software Design". The idea is that classic include guards, unlike #pragma once, do not spare you the preprocessor parsing required to ignore the header from the 2nd time on (i.e. it still has to parse and look for the start and end of the include guard. With external include guards you place the #ifdef's around the #include line itself.

So it looks like this:

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

and of course within the H file you have the classic include guard

#ifndef MY_HEADER
#define MY_HEADER

// content of header

#endif

This way the myheader.h file isn't even opened / parsed by the preprocessor, and it can save you a lot of time in large projects, especially when header files sit on shared remote locations, as they sometimes do.

again, it's all in that book. hth

愿得七秒忆 2024-07-12 08:28:23

如果您想要进行彻底的清理并且有时间,那么最好的解决方案是删除所有文件中的所有 #include(除了明显的文件,例如 abc.cpp 中的 abc.h),然后编译项目。 添加必要的前向声明或标头来修复第一个错误,然后重复直到完全完成。

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

If you want to do a complete cleanup and have the time to do it then the best solution is to delete all the #includes in all the files (except for obvious ones e.g. abc.h in abc.cpp) and then compile the project. Add the necessary forward declaration or header to fix the first error and then repeat until you comple cleanly.

This doesn't fix underlying problems that can result in include issues, but it does ensure that the only includes are the required ones.

梦冥 2024-07-12 08:28:23

理查德是对的(为什么他的解决方案被记下来?)。

无论如何,所有 C/C++ 标头都应使用内部包含防护。

这就是说,要么:

1 - 你的遗留代码不再被真正维护,你应该使用预编译头(这是一个黑客,但是嘿......你需要的是加速你的编译,而不是重构未维护的代码)

2 - 您的遗留代码仍然有效。 然后,您可以使用预编译标头和/或守卫/外部守卫作为临时解决方案,但最终,您需要删除所有包含文件(一次一个 .C 或 .CPP),然后编译每个 .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(即使它可能会慢一些:最后,前向声明无论如何都会清除无用的包含,因此您应该有一个更快的编译计时器)。

Richard was somewhat right (Why his solution was noted down?).

Anyway, all C/C++ headers should use internal include guards.

This said, either:

1 - Your legacy code is not really maintained anymore, and you should use pre-compiled headers (which are a hack, but hey... Your need is to speed up your compilation, not refactor unmaintained code)

2 - Your legacy code is still living. Then, you either use the precompiled headers and/or the guards/external guards for a temporary solution, but in the end, you'll need to remove all your includes, one .C or .CPP at a time, and compile each .C or .CPP file one at a time, correcting their includes with forward-declarations or includes when necessary (or even breaking a large include into smaller ones to be sure each .C or .CPP file will get only the headers it needs). Anyway, testing and removing obsolete includes is part of maintenance of a project, so...

My own experience with precompiled headers was not exactly a good one, because half the time, the compiler could not find a symbol I had defined, and so I tried a full "clean/rebuild", to be sure it was not the precompiled header that was obsolete. So my guess is to use it for external libraries you won't even touch (like the STL, C API headers, Boost, whatever). Still, my own experience was with Visual C++ 6, so I guess (hope?) they got it right, now.

Now, one last thing: Headers should always be self-sufficient. That means that if the inclusion of headers depends on order of inclusion, then you have a problem. For example, if you can write:

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

But not:

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

because BBB depends on AAA, then all you have is a dependency you never acknowledged in the code. Not acknowledging it with a define will only make your compilation a nightmare. BBB should include AAA, too (even if it could be somewhat slower: in the end, forward-declarations will anyway clean useless includes, so you should have a faster compile timer).

冰雪之触 2024-07-12 08:28:23

我读到,GCC 认为 #pragma Once 已弃用,尽管即使 #pragma Once 也只能做这么多来加快速度。

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

如果编译时间是您最关心的问题,您可能会考虑的另一种方法是设置预编译标头

I've read that GCC considers #pragma once deprecated, although even #pragma once can only do so much to speed things up.

To try to untangle the #include spaghetti, you can look into doxygen. It should be able to generate graphs of included headers, which may give you an edge on simplifying things. I can't recall the details offhand, but the graph features may require you to install GraphViz and tell doxygen the path where it can find GraphViz's dotty.exe.

Another approach you might consider if compile time is your primary concern is setting up Precompiled Headers.

我很OK 2024-07-12 08:28:23

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

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

Thanks for the replies, but the question is regarding existing code which includes strict "include order" etc.
The question is whether there are any tools/scripts to clarify what is actually going on.

Header guards arent the solution as they dont prevent the compiler from reading the whole file again and again and ...

计㈡愣 2024-07-12 08:28:23

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

PC-Lint will go a long way to cleaning up spaghetti headers. Also it will solve other problems for you too like uninitialised variables going unseen, etc.

生生不灭 2024-07-12 08:28:23

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

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

话又说回来,您已经回答了外部包含防护并不是您问题的答案。 对于解开必须以特定顺序包含的头文件,我建议如下:

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

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

As onebyone.livejournal.com commented in a response to your question, some compilers support include guard optimization, which the page I linked defines as follows:

The include guard optimisation is when a compiler recognises the internal include guard idiom described above and takes steps to avoid opening the file multiple times. The compiler can look at an include file, strip out comments and white space and work out if the whole of the file is within the include guards. If it is, it stores the filename and include guard condition in a map. The next time the compiler is asked to include the file, it can check the include guard condition and make the decision whether to skip the file or #include it without needing to open the file.

Then again, you already answered that external include guards are not the answer to your question. For disentangling header files that must be included in a specific order, I would suggest the following:

  • Each .c or .cpp file should #include the corresponding .h file first, and the rest of its #include directives should be sorted alphabetically. You will usually get build errors when this breaks unstated dependencies between header files.
  • If you have a header file that defines global typedefs for basic types or global #define directives that are used for most of the code, each .h file should #include that file first, and the rest of its #include directives should be sorted alphabetically.
  • When these changes cause compile errors, you will usually have to add an explicit dependency from one header file to another, in the form of an #include.
  • When these changes do not cause compile errors, they might cause behavioral changes. Hopefully you have some sort of test suite that you can use to verify the functionality of your application.

It also sounds like part of the problem might be that incremental builds are much slower than they ought to be. This situation can be improved with forward declarations or a distributed build system, as others have pointed out.

不必你懂 2024-07-12 08:28:23

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

  1. 使用预编译头
  2. 使用缓存机制(例如 scons)
  3. 使用分布式构建系统( distcc、Incredibuild($) )

Use one or more of those for speeding up the build time

  1. Use Precompiled Headers
  2. Use a caching mechanism (scons for example)
  3. Use a distributed build system ( distcc, Incredibuild($) )
又怨 2024-07-12 08:28:23

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

In headers: include headers only if you can't use forward declaration, but always #include any file that you need (include dependencies are evil!).

简单气质女生网名 2024-07-12 08:28:23

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

As mentioned in the other answer, you should definitely use forward declarations whenever possible. To my knowledge, GCC doesn't have anything equivalent to #pragma once, which is why I stick to the old fashion style of include guards.

涙—继续流 2024-07-12 08:28:23

前几天我读到了一个减少标头依赖性的巧妙技巧:编写一个脚本,该脚本将

  • 找到所有 #include 语句
  • 一次删除一个语句,
  • 如果编译失败则重新编译,将 include 语句添加回

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

更多注意事项:

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

  • gcc 支持 #pragma Once,但称其为“过时”
  • pragma Once 并非所有编译器都支持,而且不是部分编译器支持C 标准

  • 不仅编译器可能会出现问题。 像 Incredibuild 这样的工具也有 #pragma Once 的问题

I read the other day about a neat trick to reduce header dependencies: Write a script that will

  • find all #include statements
  • remove one statement at a time and recompiles
  • if compilation fails, add the include statement back in

At the end, you'll hopefully end up with the minimum of required includes in your code. You could write a similar script that re-arranges includes to find out if they are self-sufficient, or require other headers to be included before them (include the header first, see if compilation fails, report it). That should go some way to cleaning up your code.

Some more notes:

  • Modern compilers (gcc among them) recognize header guards, and optimize in the same way as pragma once would, only opening the file once.
  • pragma once can be problematic when the same file has different names in the filesystem (i.e. with soft-links)

  • gcc supports #pragma once, but calls it "obsolete"
  • pragma once isn't supported by all compilers, and not part of the C standard

  • not only compilers can be problematic. Tools like Incredibuild also have issues with #pragma once
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文