清理遗留代码“header spaghetti”
任何清理“标题意大利面”的推荐做法,这会导致极其严重的后果 编译时间慢(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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(11)
假设您熟悉“包含防护”(#ifdef 在标头开头......),加快构建时间的另一种方法是使用外部包含防护。
它在“大规模 C++ 软件设计<中进行了讨论/a>”。 这个想法是,经典的包含防护与 #pragma Once 不同,不会让您从第二次开始就忽略标头所需的预处理器解析(即它仍然必须解析并查找包含防护的开头和结尾。外部包含防护将 #ifdef 放置在 #include 行本身周围,
因此它看起来像这样:
当然,在 H 文件中,您有经典的包含防护,
这样 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:
and of course within the H file you have the classic include guard
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
如果您想要进行彻底的清理并且有时间,那么最好的解决方案是删除所有文件中的所有 #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.
理查德是对的(为什么他的解决方案被记下来?)。
无论如何,所有 C/C++ 标头都应使用内部包含防护。
这就是说,要么:
1 - 你的遗留代码不再被真正维护,你应该使用预编译头(这是一个黑客,但是嘿......你需要的是加速你的编译,而不是重构未维护的代码)
2 - 您的遗留代码仍然有效。 然后,您可以使用预编译标头和/或守卫/外部守卫作为临时解决方案,但最终,您需要删除所有包含文件(一次一个 .C 或 .CPP),然后编译每个 .C 或 .CPP 文件。 C 或 .CPP 文件一次一个,使用前向声明或必要时包含来更正其包含(或者甚至将大包含分解为较小的包含以确保每个 .C 或 .CPP 文件仅获得其所需的标头)。 无论如何,测试和删除过时的包含是项目维护的一部分,所以...
我自己在预编译头方面的经验并不完全是一个好的经验,因为有一半的时间,编译器找不到我定义的符号,所以我尝试了完整的“清理/重建”,以确保它不是过时的预编译头。 所以我的猜测是将它用于您甚至不会接触的外部库(例如 STL、C API 标头、Boost 等)。 不过,我自己的经验是使用 Visual C++ 6,所以我猜(希望?)他们现在做对了。
现在,最后一件事:标头应该始终是自给自足的。 这意味着如果标头的包含取决于包含的顺序,那么就会遇到问题。 例如,如果您可以写:
但不能写:
因为 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:
But not:
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).
我读到,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.
感谢您的回复,但问题是关于现有代码,其中包括严格的“包含顺序”等。
问题是是否有任何工具/脚本可以澄清实际发生的情况。
标头防护不是解决方案,因为它们不会阻止编译器一次又一次地读取整个文件......
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 ...
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.
正如 onebyone.livejournal.com 在回答您的问题时评论的那样,一些编译器支持 包括防护优化,我链接的页面定义如下:
话又说回来,您已经回答了外部包含防护并不是您问题的答案。 对于解开必须以特定顺序包含的头文件,我建议如下:
.c
或.cpp
文件应该#include
首先是相应的.h
文件,其余的#include
指令应按字母顺序排序。 当这破坏了头文件之间未声明的依赖关系时,您通常会收到构建错误。#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:
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:
.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.#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.#include
.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.
使用其中的一个或多个来加快构建时间
Use one or more of those for speeding up the build time
在标头中:仅当您无法使用前向声明时才包含标头,但始终 #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!).
正如另一个答案中提到的,您绝对应该尽可能使用前向声明。 据我所知,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.
前几天我读到了一个减少标头依赖性的巧妙技巧:编写一个脚本,该脚本将
最后,您希望最终在您的代码中包含最少所需的内容。 您可以编写一个类似的脚本,重新排列包含以查明它们是否是自给自足的,或者要求在它们之前包含其他标头(首先包含标头,查看编译是否失败,报告它)。 这应该有助于清理你的代码。
更多注意事项:
当同一个文件在文件系统中具有不同的名称(即带有软链接)时,pragma Once 可能会出现问题
pragma Once 并非所有编译器都支持,而且不是部分编译器支持C 标准
I read the other day about a neat trick to reduce header dependencies: Write a script that will
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:
pragma once can be problematic when the same file has different names in the filesystem (i.e. with soft-links)
pragma once isn't supported by all compilers, and not part of the C standard