#pragma是否曾经是安全的门卫?


310

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

非Windows平台(gcc)上的大多数现代编译器都支持这种功能吗?

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

#pragma once
#ifndef HEADER_H
#define HEADER_H

...

#endif // HEADER_H

我应该担心吗?我是否应该为此花费更多的精力?


3
问了类似的问题之后,我发现这#pragma once似乎避免了VS 2008中的某些类视图问题。#pragma once基于这个原因,我正在摆脱包含保护并将它们全部替换的过程。
SmacL 2011年

Answers:


188

使用#pragma once应该适用于任何现代编译器,但是我看不出没有使用标准#ifndef包含保护的任何理由。它工作正常。一个警告是,GCC #pragma once3.4版之前不支持。

我还发现,至少在GCC上,它可以识别#ifndef包含保护的标准并对其进行优化,因此它的速度不应慢于#pragma once


12
一点也不慢(无论如何使用GCC)。
杰森·可可

54
它不是那样实现的。相反,如果文件第一次以#ifndef开头并以#endif结尾,则gcc会记住该文件,并且以后总是跳过包含的内容,甚至不用费心打开文件。
杰森·可可

10
#pragma once通常更快,因为文件没有被预处理。ifndef/define/endif无论如何都需要进行预处理,因为在此块之后,您可以得到一些可编译的文件(理论上而言)
Andrey

13
有关保护宏优化的GCC文档:gcc.gnu.org/onlinedocs/cppinternals/Guard-Macros.html
Adrian

38
要使用包含保护,#ifndef FOO_BAR_H通常还必须为“ foo_bar.h”之类的文件定义一个诸如的新符号。如果以后重命名此文件,是否应该相应地调整包含保护以符合此约定?另外,如果在代码树的两个不同位置有两个不同的foo_bar.h,则必须为每个符号考虑两个不同的符号。简短的答案是使用#pragma once,如果您确实需要在不支持它的环境中进行编译,请继续为该环境添加包含保护。
Brandin 2014年

327

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


36
但是您也可以在不同位置使用两个具有相同名称的文件,而不必费心创建不同的#define NAMES,这通常是HEADERFILENAME_H的形式
Vargas

69
您还可以使用相同的#define WHATEVER来拥有两个或多个文件,这不会带来任何乐趣,这就是我希望一次使用编译指示的原因。
克里斯·黄·利弗

107
不具有说服力...将构建系统更改为不复制文件而是使用符号链接的构建系统,或者仅在每个翻译单元中的一个位置包含同一文件。听起来更像是您的基础设施混乱,必须重新组织。
Yakov Galka'5

3
而且,如果您在不同目录下具有相同名称的不同文件,则#ifdef方法将认为它们是同一文件。因此,一个存在缺点,而另一个存在缺点。
rxantos

3
@rxantos,如果文件不同,则#ifdef宏值也可以不同。
Motti 2014年

63

我希望#pragma once(或类似的东西)已经成为标准。包括后卫并不是什么大问题(但似乎很难向学习该语言的人解释),但是似乎可以避免一些小麻烦。

实际上,由于在99.98%的时间内,此#pragma once行为是所需的行为,如果防止编译器自动处理标头的多次包含,并带有#pragma或允许双重包含的内容,那将是很好的选择。

但是我们拥有我们所拥有的(除了您可能没有拥有#pragma once)。


48
我真正想要的是标准#import指令。
约翰

10
一个标准的导入指令即将到来:isocpp.org/blog/2012/11/… 但是这里还没有。我对此表示大力支持。
AHelps

6
@AHelps蒸气软件。现在已经快五年了。也许在2023年,您会回到评论中说“我告诉过您”。
doug65536 '16

它不是汽缸,而是仅在技术规范阶段。在Visual Studio 2015(blogs.msdn.microsoft.com/vcblog/2015/12/03/…)和clang(clang.llvm.org/docs/Modules.html)中实现模块。而且是导入,而不是#import。
AHelps

应该使其进入C ++ 20。
Ionoclast Brigham

36

我不知道会有任何性能上的好处,但肯定可以。我在所有C ++项目中都使用了它(当然,我使用的是MS编译器)。我发现它比使用更有效

#ifndef HEADERNAME_H
#define HEADERNAME_H
...
#endif

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

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


25

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

我认为,#pragma once与包括防护措施相比,最大的好处是避免复制/粘贴错误。

面对现实:我们大多数人几乎都不会从头开始创建新的头文件,而只是复制现有的头文件并根据需要对其进行修改。使用#pragma once而不是包含防护来创建工作模板要容易得多。我修改模板的次数越少,发生错误的可能性就越小。在不同的文件中使用相同的include防护会导致奇怪的编译器错误,并且需要花费一些时间来找出问题所在。

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


11

我使用它并对此感到满意,因为我不得不键入更少的内容来制作新的标题。对于我来说,它在以下三个平台上运行良好:Windows,Mac和Linux。

我没有任何性能信息,但我相信与分析C ++语法的速度较慢相比,#pragma和include Guard之间的区别将是什么。那是真正的问题。例如,尝试使用C#编译器编译相同数量的文件和行,以了解两者之间的区别。

最后,使用警卫或实用程序根本没有关系。


我曾经不喜欢#pragma,但是感谢您指出相对的好处...在“正常”的操作环境中,C ++解析比其他任何事物都要昂贵得多。如果存在编译时问题,则没人会从远程文件系统进行编译。
汤姆,6:

1
重新C ++解析慢度与C#。在C#中,您不必为每个微小的C ++文件解析(从字面上看)成千上万的LOC头文件(iostream,有人吗?)。使用预编译的标题可以使此问题更小,但是
Eli Bendersky 2010年

11

使用' #pragma once'可能没有任何效果(尽管越来越广泛地支持它,但并不是所有地方都支持它),因此无论如何都需要使用条件编译代码,在这种情况下,为什么要打扰' #pragma once'?编译器可能仍会对其进行优化。但是,它确实取决于您的目标平台。如果您的所有目标都支持它,那么请继续使用它-但是这应该是一个明智的决定,因为如果仅使用编译指示,然后移植到不支持该编译器的编译器,那么一切都会变得一团糟。


1
我不同意你仍然必须支持警卫队。如果您一次(或后卫)使用#pragma,这是因为没有它们就会引发一些冲突。因此,如果您的链工具不支持该项目,则该项目将无法编译,并且与您希望在旧的K&R编译器上编译一些ansi C时的情况完全相同。您只需要获取最新的chaintool或更改代码以添加一些防护即可。如果程序正在编译但无法正常工作,那将是一团糟。
kriss

5

性能优势在于,一旦读取了#pragma,就不必重新打开文件。使用警卫,编译器必须打开文件(这可能会花费很多时间),以获取不应再次包含其内容的信息。

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

无论如何,并非所有编译器都如此,因此理想情况下,一次跨平台代码必须避免使用#pragma,因为它根本不是标准的,也没有标准化的定义和效果。但是,实际上,这确实比后卫更好。

最后,更好的建议是确保同时使用编译指示和防护,而不必在这种情况下检查每个编译器的行为,从而确保从编译器获得最佳速度。

#ifndef NR_TEST_H
#define NR_TEST_H
#pragma once

#include "Thing.h"

namespace MyApp
{
 // ...
}

#endif

这样一来,您可以同时兼顾两者(跨平台和帮助编译的速度)。

由于键入时间越来越长,我个人使用了一种工具来帮助您以非常灵巧的方式生成所有内容(Visual Assist X)。


Visual Studio是否会按原样优化#include防护?其他(更好?)的编译器这样做,我想这很容易。
汤姆(Tom)2010年

1
你为什么把pragma之后ifndef?有好处吗?
user1095108 2014年

1
@ user1095108一些编译器将使用标头保护符作为分隔符,以了解文件是否仅包含必须实例化一次的代码。如果某些代码不在头文件保护范围内,则整个文件可能被视为可多次实例化。如果同一编译器一次不支持编译指示,则它将忽略该指令。因此,将编译指示放到标题保护区中一次是最通用的方法,可确保至少可以优化标题保护区。
克莱姆2014年

4

不总是。

http://gcc.gnu.org/bugzilla/show_bug.cgi?id=52566给出了一个很好的示例,说明两个文件都应包括在内,但由于时间戳和内容相同(文件名不同)而被误认为是相同的。


10
那将是编译器中的一个错误。(尝试采取不应采取的捷径)。
rxantos

4
#pragma once这是非标准的,因此无论编译器决定做什么,都是“正确的”。当然,我们可以开始讨论什么是“期望的”和什么是“有用的”。
user7610 2016年

2

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

我真的看不到它的价值如何可能会混淆旧版本的gcc,甚至其他编译器,因为没有真正的节省。我没有尝试过所有各种各样的问题,但是我敢打赌它会使许多问题混淆。

我也希望它能早日被采用,但是我可以看到这样的论点:“当ifndef运行得很好时,为什么我们需要它?”。考虑到C的许多黑暗角落和复杂性,包括后卫是最容易自我解释的事情之一。如果您对预处理器的工作原理了解甚少,那么它们应该可以自我解释。

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


2

如今,守旧派的守卫者像#pragma一样快。即使编译器未对它们进行特殊处理,当看到#ifndef WHATEVER和WHATEVER定义后,它仍将停止。今天打开文件是很便宜的。即使有改进,也要几毫秒的时间。

我只是不使用#pragma,因为它没有好处。为了避免与其他包含防护冲突,我使用类似以下内容的命令:CI_APP_MODULE_FILE_H-> CI =公司缩写;APP =应用名称;其余的不言而喻。


19
输入更少的好处不是吗?
Andrey

1
请注意,尽管如此,十万分之一秒是几分钟。大型项目包含一万个文件,每个文件包含数十个标头。考虑到当今的多核CPU,输入/输出(尤其是打开许多小文件)是主要的瓶颈之一。
戴蒙

1
“如今,守旧派的守卫者像#pragma一样快。” 今天,也是很多年前。GCC网站上最古老的文档适用于2001年的2.95版本,当时优化包含防护措施并不新鲜。这不是最近的优化。
Jonathan Wakely 2015年

4
主要好处是,包括防护措施易于出错且容易出错。在不同目录中拥有两个具有相同名称的不同文件(而且包含保护可能是相同的符号),或者在复制包含保护时产生复制粘贴错误都太容易了。Pragma曾经不那么容易出错,并且可以在所有主要的PC平台上运行。如果可以使用,那是更好的样式。
AHelps

2

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

也就是说,如今的编译器(包括GCC)足够聪明,可以一次将杂乱无章的内容包括在内。即他们不打开文件,避免文件IO损失。

在不支持编译指示的编译器中,我看到了一些麻烦的手动实现。

#ifdef FOO_H
#include "foo.h"
#endif

我个人喜欢#pragma一旦方法,因为它避免了命名冲突和潜在的拼写错误的麻烦。相比之下,它也是更优雅的代码。就是说,对于可移植代码,除非编译器对此有所抱怨,否则两者都不会受到损害。


1
“也就是说,这些天的编译器(包括GCC)足够聪明,可以一次将杂乱无章的事物包括在内。” 他们已经这样做了几十年,甚至比#pragma once现在还更长!
Jonathan Wakely 2015年

以为你误会了我。我的意思是说在编译前一次,所有编译器都会在预处理器阶段对同一h文件包含多个IO费用。现代实现最终会在预处理器阶段使用更好的文件缓存。无论如何,如果没有编译指示,预处理器阶段最终仍会包括include保护程序之外的所有内容。一次使用编译指示,整个文件将被忽略。从这个角度来看,实用主义仍然是有利的。
Shammi 2015年

1
不,这是错误的,即使没有#pragma,得体的编译器也会将整个文件留在外面,他们不会第二次打开文件,甚至根本不会第二次查看文件,请参阅gcc.gnu.org/onlinedocs/ cpp / Once-Only-Headers.html(与缓存无关)。
Jonathan Wakely

1
从您的链接来看,优化似乎只发生在cpp中。无论如何,缓存确实起作用。预处理器如何知道将代码包含在保护范围之外。例子... extern int foo; #ifndef INC_GUARD #define INC_GUARD class ClassInHeader {}; #endif在这种情况下,预处理器将必须包含extern int foo; 如果您多次包含相同的文件,则需要多次。归根结底,只要我们了解一次#pragma之间的区别并包含防护以及各种编译器对它们的行为,就可以对此进行争论:)
Shammi 2015年

1
显然,它没有在其中应用优化。
Jonathan Wakely 2015年

1

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

源文件名通常与类名相同,而且我们知道,有时我们需要refactor,以重命名类名,然后我们也必须更改类名#include XXXX,因此我认为手动维护#include xxxxx并不是一项明智的工作。即使使用Visual Assist X扩展名,维护“ xxxx”也不是必需的工作。


1

给人们以为始终需要自动一次性包含头文件的人们的附加说明:几十年来,我使用头文件的双重或多重包含来构建代码生成器。特别是对于生成协议库存根,我发现拥有一个极其可移植且功能强大的代码生成器而无需其他工具和语言,这让我感到非常舒适。X-Macros博客显示,我不是唯一使用此方案的开发人员。没有缺少的自动防护,这是不可能的。


C ++模板可以解决问题吗?由于C ++模板的方式,我很少发现对宏有任何有效的需求。
清晰的时间为

1
我们的长期专业经验是,始终使用成熟的语言,软件和工具基础结构,使我们作为服务提供商(嵌入式系统)在生产率和灵活性方面均具有巨大优势。相反,开发基于C ++嵌入式系统软件和堆栈的竞争对手可能会发现一些开发人员对工作更满意。但是我们通常会在上市时间,功能和灵活性方面多次胜过它们。一遍又一遍地使用一种和同一种工具,就不会低估生产率的提高。相反,Web开发确实会遭受许多框架的困扰。
Marcel

不过请注意:在每个头文件中都没有针对DRY原理本身包含guards /#pragma。我可以在X-Macro功能中看到您的观点,但这不是include的主要用途,如果我们坚持使用DRY,不是应该像header unguard /#pragma multi这样吗?
caiohamamura

DRY代表“不要重复自己”。它是关于人类的。机器在做什么,与该范例无关。C ++模板重复很多,C编译器也这样做(例如循环展开),并且每台计算机都经常且快速地重复几乎所有令人难以置信的重复。
Marcel
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.