关于c ++:#pragma曾经是一个安全的包含守卫吗?

Is #pragma once a safe include guard?

我已经读过使用#pragma once时会有一些编译器优化,这会导致更快的编译。 我认为这是非标准的,因此可能造成跨平台兼容性问题。

这是非Windows平台(gcc)上大多数现代编译器支持的东西吗?

我想避免平台编译问题,但也想避免后备警卫的额外工作:

1
2
3
4
5
6
7
#pragma once
#ifndef HEADER_H
#define HEADER_H

...

#endif // HEADER_H

我应该担心吗? 我是否应该在这方面进一步消耗精力?


#pragma once确实有一个缺点(除非是非标准的),如果你在不同的位置有相同的文件(我们有这个因为我们的构建系统复制文件),那么编译器会认为这些是不同的文件。


使用#pragma once应该适用于任何现代编译器,但我认为没有任何理由不使用标准的#ifndef include guard。它工作得很好。需要注意的是,GCC在版本3.4之前不支持#pragma once

我还发现,至少在GCC上,它识别标准#ifndef包含保护并对其进行优化,因此它不应该比#pragma once慢得多。


我希望#pragma once(或类似的东西)符合标准。包括警卫不是一个真正的大问题(但他们似乎有点难以向学习该语言的人解释),但这似乎是一个可以避免的轻微烦恼。

事实上,由于99.98%的时间,#pragma once行为是理想的行为,如果编译器自动处理阻止多个包含头的话会很好,使用#pragma或允许双重包含的内容。

但是我们拥有的东西(除了你可能没有#pragma once)。


我不知道任何性能优势,但它确实有效。我在所有的C ++项目中使用它(授予我使用MS编译器)。我发现它比使用更有效

1
2
3
4
#ifndef HEADERNAME_H
#define HEADERNAME_H
...
#endif

它执行相同的工作,并且不会使用其他宏填充预处理器。

从版本3.4开始,GCC正式支持#pragma once


自3.4以来,GCC支持#pragma once,请参阅http://en.wikipedia.org/wiki/Pragma_once以获得进一步的编译器支持。

我在使用#pragma once而不是包含警卫时看到的巨大好处是避免复制/粘贴错误。

让我们面对现实:我们大多数人几乎不能从头开始新的头文件,而只是复制现有的头文件并根据我们的需要进行修改。使用#pragma once而不是包含保护来创建工作模板要容易得多。我修改模板的次数越少,我就越不容易遇到错误。在不同的文件中包含相同的防护导致奇怪的编译器错误,并且需要一些时间来弄清楚出了什么问题。

TL; DR:#pragma once更易于使用。


我使用它并且我很满意它,因为我必须输入更少的东西来制作新的标题。它在三个平台上运行良好:Windows,Mac和Linux。

我没有任何性能信息,但我相信#pragma和include guard之间的区别与解析C ++语法的速度相比毫无结果。这是真正的问题。例如,尝试使用C#编译器编译相同数量的文件和行,以查看差异。

最后,使用后卫或者pragma,根本不重要。


使用'#pragma once'可能没有任何影响(它在任何地方都不受支持 - 尽管它得到越来越广泛的支持),所以你需要使用条件编译代码,在这种情况下,为什么要打扰'#pragma once'?无论如何,编译器可能会优化它。但它确实依赖于您的目标平台。如果你的所有目标都支持它,那么继续使用它 - 但它应该是一个有意识的决定,因为如果你只使用pragma然后移植到不支持它的编译器,那么所有地狱都会破裂。


性能优势在于,一旦读取了#pragma,就不必重新打开文件。使用警卫,编译器必须打开文件(时间成本很高)才能获得不应再包含内容的信息。

这是理论,因为对于每个编译单元,一些编译器将自动不打开没有任何读取代码的文件。

无论如何,对于所有编译器来说都不是这样,所以理想情况下,跨平台代码必须避免使用#pragma,因为它根本不是标准的/没有标准化的定义和效果。然而,实际上,它确实比守卫更好。

最后,你可以确保从编译器获得最佳速度而不必在这种情况下检查每个编译器的行为,这是一个更好的建议,就是使用pragma once和guard。

1
2
3
4
5
6
7
8
9
10
11
12
#ifndef NR_TEST_H
#define NR_TEST_H
#pragma once

#include"Thing.h"

namespace MyApp
{
 // ...
}

#endif

这样你就可以获得最佳效果(跨平台和帮助编译速度)。

由于打字时间较长,我个人使用一种工具来帮助以一种非常邪恶的方式生成所有这些(Visual Assist X)。


不总是。

http://gcc.gnu.org/bugzilla/show_bug.cgi?id=52566有两个文件的好例子,两个文件都包含在内,但由于时间戳和内容相同(文件名不同),误认为是相同的。


主要区别在于编译器必须打开头文件才能读取包含保护。相比之下,pragma曾经导致编译器跟踪文件,并且当遇到同一文件的另一个包含时不执行任何文件IO。虽然这听起来可以忽略不计,但它可以轻松扩展到大型项目,特别是那些没有良好标题的项目包括学科。

也就是说,这些天编译器(包括海湾合作委员会)足够聪明,可以像pragma一样对待包括守卫。即他们不打开文件并避免文件IO惩罚。

在不支持pragma的编译器中,我看到手动实现有点麻烦。

1
2
3
#ifdef FOO_H
#include"foo.h"
#endif

我个人喜欢#pragma曾经的方法,因为它避免了命名冲突和潜在拼写错误的麻烦。相比之下,它也是更优雅的代码。也就是说,对于可移植代码,除非编译器抱怨它,否则不应该受到影响。


今天老派包括守卫和#pragma一样快。即使编译器没有特别处理它们,它仍然会在看到#ifndef WHATEVER并定义WHATEVER时停止。今天打开文件很便宜。即使有改进,它也会达到毫秒级。

我只是不使用#pragma一次,因为它没有任何好处。为避免与其他包含警卫发生冲突,我使用如下内容:CI_APP_MODULE_FILE_H - > CI =公司缩写; APP =申请名称;其余的是不言自明的。


在非常大的树上使用gcc 3.4和4.1(有时使用distcc),当使用#pragma代替或与标准包含警卫结合使用时,我还没有看到任何加速。

我真的不知道它的价值如何可能混淆旧版本的gcc,甚至其他编译器,因为没有真正的节省。我没有尝试过各种各样的指针,但我愿意打赌它会让很多人感到困惑。

我也希望它早日被采用,但我可以看到"当ifndef工作得很好时,为什么我们需要它?"。考虑到C的许多黑暗角落和复杂性,包括守卫是最简单,自我解释的事情之一。如果您对预处理器的工作原理知之甚少,那么它们应该是自我解释的。

但是,如果您确实观察到了显着的加速,请更新您的问题。


人们认为总是需要自动一次性包含头文件的附加说明:我使用了几十年的头文件的双重或多重构建代码生成器。 特别是对于协议库存根的生成,我发现拥有一个非常便携且功能强大的代码生成器而没有其他工具和语言非常舒适。 我不是唯一使用此方案的开发人员,因为这个博客是X-Macros节目。 如果没有丢失的自动防护,这是不可能做到的。


如果我们使用msvc或Qt(最高Qt 4.5),由于GCC(最多3.4),msvc都支持#pragma once,我看不出没有使用#pragma once的理由。

源文件名通常是相同的类名,而且我们知道,有时我们需要重构,重命名类名,那么我们也必须更改#include XXXX,所以我认为手动维护#include xxxxx不是一个聪明的工作。即使使用Visual Assist X扩展,维护"xxxx"也不是必要的工作。