#pragma曾经vs包括卫兵?
我正在研究一个只能在Windows上运行的代码库,并在Visual Studio下编译(它与Excel紧密集成,因此不会在任何地方)。 我想知道是否应该使用传统的包含守卫或使用#pragma once
为我们的代码。 我认为让编译器处理#pragma once
会产生更快的编译,并且在应对和粘贴时不易出错。 它也稍微不那么丑陋;)
注意:为了获得更快的编译时间,我们可以使用Redundant Include Guards,但是增加了包含文件和包含文件之间的紧密耦合。 通常这是可以的,因为守卫应该基于文件名,并且只有在你需要改变包含名时才会改变。
我不认为这会在编译时产生重大影响,但是#pragma once
在编译器中得到很好的支持,但实际上并不是标准的一部分。 预处理器可能会稍微快一点,因为它更容易理解您的确切意图。
#pragma once
不太容易犯错,而且input的代码也较less。
为了加快编译时间,只需向前声明,而不是包含在.h文件中。
我更喜欢使用#pragma once
。
看到这个维基百科文章关于使用两者的可能性 。
我只是想补充一下这个讨论,我只是在VS和GCC上进行编译,并且用来使用include guard。 现在我已经切换到#pragma once
,对我来说唯一的原因不是性能,可移植性或标准,因为我不关心什么是标准的,只要VS和GCC支持它,那就是:
#pragma once
减less了错误的可能性。
将一个头文件复制并粘贴到另一个头文件是非常容易的,可以根据需要对其进行修改,而忘记修改包含的名字。 一旦两者都包含在内,则需要一段时间来追踪错误,因为错误消息不一定清楚。
#pragma once
有不可修复的错误。 它不应该被使用。
如果你的#include
searchpath足够复杂,编译器可能无法区分具有相同基本名称的两个头文件(例如a/foo.h
和b/foo.h
),所以一个#pragma once
将压制两者 。 它也可能无法说明两个不同的相对包含(例如#include "foo.h"
和#include "../a/foo.h"
#include "foo.h"
#include "../a/foo.h"
是指同一个文件,所以#pragma once
将无法抑制冗余包括它应该有的时间。
这也影响编译器避免用#ifndef
看守重读文件的能力,但这只是一个优化。 使用#ifndef
警卫,编译器可以安全地读取任何不确定的文件; 如果这是错误的,只需要做一些额外的工作。 只要没有两个头文件定义相同的守护macros,代码将按照预期进行编译。 如果两个头文件定义了相同的守护macros,程序员可以进入并更改其中的一个。
#pragma once
没有这样的安全网 – 如果编译器对头文件的身份是错误的,那么程序将无法编译。 如果你遇到这个错误,你唯一的select是停止使用#pragma once
,或者重命名一个头文件。 标题名称是API合约的一部分,所以重命名可能不是一个选项。
(为什么这是不可修复的简短版本是,Unix和Windows文件系统API都没有提供任何保证告诉你两个绝对path是否指向同一个文件的机制,如果你觉得inode号码可以用于那个,对不起,你错了。)
(历史logging:在12年前,当我有权这样做的时候,我并没有翻译#pragma once
和#import
输出GCC的唯一原因是苹果的系统头依赖于它们。回想起来,这不应该阻止了我。)
(由于现在在评论主题中已经出现了两次:GCC开发者确实花费了相当多的努力使得#pragma once
尽可能可靠;参见GCC bug报告11569 ,但是在当前版本的GCC中的实现仍然可以在合理的条件下失败 ,比如build立时钟偏移的农场,我不知道其他编译器的实现是什么样的,但我不希望任何人做得更好) 。
直到#pragma once
成为标准(目前还不是未来标准的优先级),我build议你使用它并使用警卫,如下所示:
#ifndef BLAH_H #define BLAH_H #pragma once // ... #endif
原因是:
-
#pragma once
不是标准的,所以有些编译器可能不提供这些function。 也就是说,所有主要的编译器都支持它。 如果一个编译器不知道它,至less它会被忽略。 - 由于
#pragma once
没有标准行为,所以不应该假设所有编译器的行为都是一样的。 警卫至less要确保所有编译器的基本假设都是一样的,至less可以执行所需的预警指令。 - 在大多数编译器中,
#pragma once
会加快编译(一个cpp),因为编译器不会重新打开包含这条指令的文件。 所以根据编译器的不同,把它放在一个文件中可能会有所帮助。 我听说g ++可以做到相同的优化,当检测到守卫,但它必须得到证实。
一起使用这两个你得到每个编译器的最好的。
现在,如果你没有一些自动的脚本来生成警卫,那么使用#pragma once
可能会更方便#pragma once
。 只要知道这对便携式代码意味着什么。 (我使用VAssistX来快速生成警卫和编译指示)
你应该几乎总是以便携的方式来思考你的代码(因为你不知道未来是由什么构成的),但是如果你真的认为它不是用另一个编译器编译的话(例如非常特殊的embedded式硬件代码)那么你应该只检查你的编译器文档关于#pragma once
以知道你真的在做什么。
如果你肯定你永远不会在不支持它的编译器中使用这个代码(Windows / VS,GCC和Clang是支持它的编译器的例子),那么你一定可以使用#pragma而不用担心。
您也可以使用两者(请参阅下面的示例),以便在兼容的系统上获得可移植性和编译速度
#pragma once #ifndef _HEADER_H_ #define _HEADER_H_ ... #endif
从软件testing人员的angular度来看
#pragma once
比一个包含守护程序短,错误less,大多数编译器支持,有些人说它编译速度更快(这不是真的[不再]]。
但我仍然build议你去标准#ifndef
包括警卫。
为什么#ifndef
?
考虑一个像这样的人为的类层次结构,其中每个类A
, B
和C
存在于它自己的文件中:
啊
#ifndef A_H #define A_H class A { public: // some virtual functions }; #endif
BH
#ifndef B_H #define B_H #include "ah" class B : public A { public: // some functions }; #endif
CH
#ifndef C_H #define C_H #include "bh" class C : public B { public: // some functions }; #endif
现在让我们假设你正在为你的类编写testing,并且你需要模拟真正复杂的类B
的行为。 一种方法是使用例如google mock来编写一个模拟类 ,并将其放在目录mocks/bh
。 请注意,类名没有改变,但只存储在不同的目录中。 但是最重要的是,包含后卫的名字和原始文件bh
完全一样。
嘲笑/ BH
#ifndef B_H #define B_H #include "ah" #include "gmock/gmock.h" class B : public A { public: // some mocks functions MOCK_METHOD0(SomeMethod, void()); }; #endif
有什么好处?
通过这种方法,您可以嘲笑B
class的行为,而不必触摸原始class级或告诉C
你所要做的就是把目录mocks/
放在编译器的includepath中。
为什么不能用#pragma once
做#pragma once
?
如果你曾经使用过#pragma once
,你会得到一个名字冲突,因为它不能保护你不能定义类B
两次,一次是原始的,一次是模拟的版本。
我一般不会打扰#pragma once
因为我的代码有时不得不使用MSVC或GCC以外的其他编译器(embedded式系统的编译器并不总是有#pragma)。
所以我必须使用#include警卫。 我也可以使用#pragma once
,只是一些答案build议,但似乎没有太多的理由,它会经常导致不支持它的编译器不必要的警告。
我不确定什么时候可以节省杂注。 我听说编译器通常已经认识到,除了警卫macros之外,头文件只有注释,并且在这种情况下会执行相同的#pragma once
(即,从不再处理该文件)。 但是我不确定这是否是真的,或者只是一个编译器可以做这种优化。
无论哪种情况,对我来说使用#include警卫会更容易,这些警卫可以在任何地方工作,而不用担心。
我想你应该做的第一件事是检查这是否真的会有所作为,即。 你应该首先testing性能。 谷歌的search之一扔了这个 。
在结果页面中,对于我来说,这些列很不起眼,但很明显,至lessVC6微软并没有实现其他工具正在使用的包括守护进程优化。 在内部守卫的情况下,内部守卫是外部守卫的50倍(外部守卫至less和#pragma一样好)。 但是让我们考虑一下这个可能的影响:
根据所提供的表格,打开包含的时间和检查它是#pragma等价物的50倍。 但是在1999年,每个文件的实际测量时间是1微秒。
那么,一个TU有多less个重复的头文件呢? 这取决于你的风格,但如果我们说一个平均TU有100个重复,那么在1999年,我们可能支付每个TU 100微秒。 硬盘的改进目前可能要低得多,但即使如此,即使是预编译的头文件和正确的依赖关系跟踪,项目的总累积成本几乎肯定是您构build时间的一个极其重要的部分。
现在,另一方面,尽可能不太可能,如果您曾经迁移到一个不支持#pragma once
的编译器,那么考虑将整个源代码库更新为包含守护进程而不是#编译?
没有理由说微软不能像GCC和其他编译器那样实现一个包含守护进程的优化(实际上任何人都可以确认他们的更新版本是否实现了这一点)。 恕我直言, #pragma once
做的很less,除了限制你select的替代编译器。
有一个相关的问题 , 我回答 :
#pragma once
有一个缺点(除非是非标准的),也就是说,如果你在不同的位置有相同的文件(我们有这个是因为我们的构build系统复制文件),那么编译器会认为这是不同的文件。
如果有人绊倒了这个问题,而不是另一个,我也在这里添加答案。
在对#pragma once
和#ifndef
卫士之间所假设的性能权衡与正确与否的论证进行了广泛的讨论之后(我为了这个目的而在一些相对较新的灌输基础上考虑了#pragma once
一边),我决定最后testing#pragma once
更快的理论,因为编译器不需要重新包含已经包含的文件。
为了testing,我自动生成了500个具有复杂相关性的头文件,并且有一个.c
文件,包含了所有这些文件。 我用三种方法进行testing,一次只用#ifndef
,一次只用#pragma once
,一次用两次。 我在一个相当现代化的系统(运行OSX的2014 MacBook Pro,使用XCode捆绑的Clang和内部SSD)上进行了testing。
一,testing代码:
#include <stdio.h> //#define IFNDEF_GUARD //#define PRAGMA_ONCE int main(void) { int i, j; FILE* fp; for (i = 0; i < 500; i++) { char fname[100]; snprintf(fname, 100, "include%dh", i); fp = fopen(fname, "w"); #ifdef IFNDEF_GUARD fprintf(fp, "#ifndef _INCLUDE%d_H\n#define _INCLUDE%d_H\n", i, i); #endif #ifdef PRAGMA_ONCE fprintf(fp, "#pragma once\n"); #endif for (j = 0; j < i; j++) { fprintf(fp, "#include \"include%dh\"\n", j); } fprintf(fp, "int foo%d(void) { return %d; }\n", i, i); #ifdef IFNDEF_GUARD fprintf(fp, "#endif\n"); #endif fclose(fp); } fp = fopen("main.c", "w"); for (int i = 0; i < 100; i++) { fprintf(fp, "#include \"include%dh\"\n", i); } fprintf(fp, "int main(void){int n;"); for (int i = 0; i < 100; i++) { fprintf(fp, "n += foo%d();\n", i); } fprintf(fp, "return n;}"); fclose(fp); return 0; }
而现在,我的各种testing运行:
folio[~/Desktop/pragma] fluffy$ gcc pragma.c -DIFNDEF_GUARD folio[~/Desktop/pragma] fluffy$ ./a.out folio[~/Desktop/pragma] fluffy$ time gcc -E main.c > /dev/null real 0m0.164s user 0m0.105s sys 0m0.041s folio[~/Desktop/pragma] fluffy$ time gcc -E main.c > /dev/null real 0m0.140s user 0m0.097s sys 0m0.018s folio[~/Desktop/pragma] fluffy$ time gcc -E main.c > /dev/null real 0m0.193s user 0m0.143s sys 0m0.024s folio[~/Desktop/pragma] fluffy$ gcc pragma.c -DPRAGMA_ONCE folio[~/Desktop/pragma] fluffy$ ./a.out folio[~/Desktop/pragma] fluffy$ time gcc -E main.c > /dev/null real 0m0.153s user 0m0.101s sys 0m0.031s folio[~/Desktop/pragma] fluffy$ time gcc -E main.c > /dev/null real 0m0.170s user 0m0.109s sys 0m0.033s folio[~/Desktop/pragma] fluffy$ time gcc -E main.c > /dev/null real 0m0.155s user 0m0.105s sys 0m0.027s folio[~/Desktop/pragma] fluffy$ gcc pragma.c -DPRAGMA_ONCE -DIFNDEF_GUARD folio[~/Desktop/pragma] fluffy$ ./a.out folio[~/Desktop/pragma] fluffy$ time gcc -E main.c > /dev/null real 0m0.153s user 0m0.101s sys 0m0.027s folio[~/Desktop/pragma] fluffy$ time gcc -E main.c > /dev/null real 0m0.181s user 0m0.133s sys 0m0.020s folio[~/Desktop/pragma] fluffy$ time gcc -E main.c > /dev/null real 0m0.167s user 0m0.119s sys 0m0.021s folio[~/Desktop/pragma] fluffy$ gcc --version Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.12.sdk/usr/include/c++/4.2.1 Apple LLVM version 8.1.0 (clang-802.0.42) Target: x86_64-apple-darwin17.0.0 Thread model: posix InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
正如你所看到的那样, #pragma once
的版本比#ifndef
的版本要快一些, 但是差异是微不足道的,而且会被实际构build和链接代码的时间所掩盖采取。 也许有了足够大的代码库,实际上可能会导致构build时间差几秒钟,但现代编译器之间可以优化#ifndef
警卫,操作系统有良好的磁盘caching,以及存储技术的速度越来越快,性能方面的争论似乎是没有意义的,至less在今天这样一个典型的开发者系统上。 更老更奇特的构build环境(例如,在networking共享上托pipe的标题,从磁带构build等)可能会稍微改变方程式,但在这种情况下,首先简单地创build一个不太脆弱的构build环境似乎更有用。
事实是, #ifndef
是标准化的标准行为,而#pragma once
不是, #ifndef
也处理奇怪的文件系统和searchpath的angular落情况,而#pragma once
可以得到非常困惑的某些事情,导致不正确的行为程序员无法控制。 #ifndef
的主要问题是程序员为他们的守卫select了不好的名字(名称冲突等等),即使如此,API的使用者很可能使用#undef
覆盖那些可怜的名字 – 这可能不是一个完美的解决scheme,但它是可能的 ,而#pragma once
没有追索权,如果编译器错误地剔除#include
。
因此, 即使 #pragma once
明显地(稍快)一些,但我并不认为这本身就是在#ifndef
卫兵上使用它的原因。
编辑 :感谢来自@LightnessRacesInOrbit的反馈我增加了头文件的数量,并改变了testing,只运行预处理步骤,消除了编译和链接过程中添加任何less量的时间(这是微不足道的,现在不存在)。 如预期的那样,差别大致相同。
#pragma once
允许编译器在文件再次出现时完全跳过文件,而不是在文件达到#include警卫之前parsing文件。
因此,语义有些不同,但是如果按照打算使用的方式使用它们,它们是相同的。
把两者结合起来可能是最安全的路线,就像在最糟糕的情况下(编译器将未知编译标记标记为实际错误,而不仅仅是警告)一样,您只需要删除#pragma就可以了。
当你限制你的平台时,说“在桌面上的主stream编译器”,你可以放心地忽略#include警卫,但是我也感到不安。
OT:如果你有其他的技巧/经验来分享加速构build,我会很好奇。
对于那些想要使用#pragma一次并包含警卫的人:如果您不使用MSVC,那么您将不会从#pragma获得一次优化。
而且你不应该把“#pragma once”放入一个包含多次的头文件中,每个包含文件可能有不同的效果。
以下是关于#pragma一次性使用的示例的详细讨论。
在上面Konrad Kleine的解释。
简要总结:
- 当我们使用
# pragma once
编译器承担了很大的责任,不允许多次包含它。 这意味着,在提到文件中的代码片段之后,它不再是您的责任。
现在,编译器在文件的开头查找这个代码片段,并跳过它(如果已经包含一次)。 This definitely will reduce the compilation-time (on an average and in huge-system). However, in case of mocks/test environment, will make the test-cases implementation difficult, due to circular etc dependencies.
- Now, when we use the
#ifndef XYZ_H
for the headers, it is more of the developers responsibility to maintain the dependency of headers. Which means, whenever due to some new header file, there is possibility of the circular dependency, compiler will just flag some "undefined ..
" error messages at compile time, and it is user to check the logical connection/flow of the entities and rectify the improper includes.
This definitely will add to the compilation time (as needs to rectified and re-run). Also, as it works on the basis of including the file, based on the "XYZ_H" defined-state, and still complains, if not able to get all the definitions.
Therefore, to avoid situations like this, we should use, as;
#pragma once #ifndef XYZ_H #define XYZ_H ... #endif
ie the combination of both.