我已经读到使用#pragma once时会进行一些编译器优化,这可能会加快编译速度。我认识到这是非标准的,因此可能会引起跨平台兼容性问题。

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

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

#pragma once
#ifndef HEADER_H
#define HEADER_H

...

#endif // HEADER_H


我应该担心吗?我应该在这方面花费更多的精力吗?

评论

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

#1 楼

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

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

评论


绝对不应该慢一点(无论如何使用GCC)。

–Jason Coco
09年4月24日在21:07

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

–Jason Coco
09年4月25日在1:32

#pragma一次通常更快,因为文件没有被预处理。 ifndef / define / endif无论如何都需要预处理,因为在此块之后,您可以拥有一些可编译的东西(理论上)

–安德烈
2011年3月3日在21:57

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

–阿德里安
2011年5月17日15:31

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

–白兰地
2014年6月22日7:45



#2 楼

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

评论


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

–瓦加斯
2010年7月30日在12:39

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

–克里斯·黄·利弗(Chris Huang-Leaver)
2011-09-21 14:52

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

–亚科夫·加尔卡(Yakov Galka)
2012年5月11日19:04

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

–rxantos
2014年4月24日4:50

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

–莫蒂
14-4-24在6:59



#3 楼

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

在实际上,由于在99.98%的时间内,#pragma once行为是所需的行为,如果编译器使用#pragma或防止重复包含的内容自动阻止了头的多次包含,那将是一件很不错的事情。
但是我们拥有的(除了您可能没有#pragma once)。

评论


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

–约翰
13年7月19日在15:21

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

–A帮助
2015年4月3日在18:34

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

– Doug65536
16 Dec 10'在10:28

应该使它进入C ++ 20。

–电离破碎的布里格姆
17年9月2日在4:22

...并将其纳入C ++ 20。

– LNJ
20-6-29在9:02

#4 楼

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

#ifndef HEADERNAME_H
#define HEADERNAME_H
...
#endif
更有效。

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

#5 楼

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

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

#6 楼

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

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

最后,使用防护或编译指示完全没有关系。 />

评论


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

–汤姆
10 Mar 4 '10在6:03

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

– Eli Bendersky
10-10-28在16:55

#7 楼

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

评论


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

– kriss
16年7月21日在16:56

#8 楼

性能优势在于,一旦读取了#pragma,就不必重新打开文件。有了警卫,编译器不得不打开文件(这可能会花费很长时间)以获取不应再次包含其内容的信息。对于每个编译单元,打开的文件都没有任何读取的代码。它根本不是标准的/没有标准化的定义和效果。但是,实际上,它实际上比保护程序要好。在这种情况下,最好同时使用一次编译指示和防护。

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

评论


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

–汤姆
10 Mar 4 '10 at 6:11

您为什么要在ifndef之后放上编译指示?有好处吗?

–user1095108
14年5月13日在23:04

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

–克莱姆
2014年5月14日晚上10:52

#9 楼

并非总是如此。相同的时间戳记和内容(不相同的文件名)。

评论


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

–rxantos
2014年4月24日4:47

#pragma一次是非标准的,因此编译器决定执行的操作都是“正确的”。当然,然后我们可以开始讨论什么是“期望的”和什么是“有用的”。

–user7610
16年1月9日在21:32

#10 楼

在非常大的树上使用gcc 3.4和4.1(有时使用distcc)时,我还没有看到一次使用#pragma代替标准的include防护或与之结合使用时的速度提高。

我真的看不到它的价值如何可能会混淆旧版本的gcc,甚至其他编译器,因为没有真正的节省。我还没有尝试过所有各种各样的问题,但是我敢打赌它会使许多问题混淆。 “当ifndef运行得很好时,为什么我们需要它?”。考虑到C的许多黑暗角落和复杂性,包括后卫是最容易自我解释的事情之一。如果您对预处理器的工作原理甚至不甚了解,那么它们应该可以自我解释。

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

#11 楼

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

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

评论


输入更少的好处不是吗?

–安德烈
2011年9月1日23:20在

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

–达蒙
2013年9月14日14:02在

“如今,守旧派守卫者的速度与#pragma一样快。”今天,也是很多年前。 GCC网站上最古老的文档适用于2001年的2.95版本,那时优化包含卫兵的工作并不新鲜。这不是最近的优化。

–乔纳森·韦克利(Jonathan Wakely)
15年3月17日在11:33

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

–A帮助
15年4月3日在18:38

#12 楼

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

在不支持编译指示的编译器中,我看到了一些麻烦的手动实现。 br />
我个人喜欢#pragma一旦访问,因为它避免了命名冲突和潜在的拼写错误的麻烦。相比之下,它也是更优雅的代码。就是说,对于可移植的代码,除非编译器对此有所抱怨,否则两者都不会受到损害。

评论


“也就是说,这些天的编译器(包括GCC)足够聪明,可以一次将杂乱无章的事物包括在内。”他们已经这样做了数十年,可能比#pragma曾经存在的时间更长!

–乔纳森·韦克利(Jonathan Wakely)
15年3月17日在11:30

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

– Shammi
15年3月18日在15:14

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

–乔纳森·韦克利(Jonathan Wakely)
2015年3月18日在16:03



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

– Shammi
15年3月19日在17:21



显然,它没有在其中应用优化。

–乔纳森·韦克利(Jonathan Wakely)
15年3月19日在17:48

#13 楼

如果我们使用msvc或Qt(不超过Qt 4.5),由于GCC(不超过3.4),msvc都支持#pragma once,因此我看不出不使用#pragma once的原因。
源文件名通常相同类名,而且我们知道,有时我们需要重构,以重命名类名,然后我们还必须更改#include XXXX,因此我认为手动维护#include xxxxx不是一件明智的工作。即使使用Visual Assist X扩展名,也不必维护“ xxxx”。

#14 楼

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

评论


C ++模板能否解决问题?由于C ++模板的方式,我很少发现对宏有任何有效的需求。

–更清晰
17年12月15日在11:27

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

–马塞尔
17年12月19日在10:34

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

– caiohamamura
20-2-13在14:00

DRY代表“不要重复自己”。它是关于人类的。机器在做什么,与该范例无关。 C ++模板重复很多次,C编译器也这样做(例如循环展开),并且每台计算机都经常且快速地重复几乎难以置信的所有操作。

–马塞尔
20年3月2日,9:30