#pragma once 与包括警卫?

新手上路,请多包涵

我正在开发一个已知只能在 Windows 上运行并在 Visual Studio 下编译的代码库(它与 excel 紧密集成,因此它不会去任何地方)。我想知道我是否应该使用传统的包含防护或使用 #pragma once 作为我们的代码。我认为让编译器处理 #pragma once 会产生更快的编译,并且在复制和粘贴时更不容易出错。它也稍微不那么难看 ;)

注意:为了获得更快的编译时间,我们可以使用 Redundant Include Guards ,但这会增加包含文件和包含文件之间的紧密耦合。通常没关系,因为防护应该基于文件名,并且只有在您需要更改包含名称时才会更改。

原文由 Matt Price 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 554
2 个回答

我认为它不会对编译时间产生显着影响,但是 #pragma once 在编译器中得到了很好的支持,但实际上并不是标准的一部分。预处理器可能会更快一点,因为它更容易理解您的确切意图。

#pragma once 不太容易出错,并且输入的代码更少。

为了加快编译时间,只需转发声明而不是尽可能包含在 .h 文件中。

我更喜欢使用 #pragma once

请参阅此 wikipedia 文章,了解同时使用这两种方法的可能性

原文由 Brian R. Bondy 发布,翻译遵循 CC BY-SA 2.5 许可协议

在就 #pragma once#ifndef 守卫与正确与否的争论(我站在 #pragma once a0bf47e8a6c82f98c04b1784cc1aa6e 之间)进行了扩展讨论之后基于一些相对最近的灌输),我决定最终测试 #pragma once 更快的理论,因为编译器不必尝试重新 #include 一个文件已经包括在内了。

为了测试,我自动生成了 500 个具有复杂相互依赖关系的头文件,并且有一个 .c 文件,其中 #include 全部包含在内。我以三种方式运行测试,一次只使用 #ifndef ,一次只使用 #pragma once ,一次使用两者。我在一个相当现代的系统上进行了测试(运行 OSX 的 2014 MacBook Pro,使用 XCode 捆绑的 Clang,带有内部 SSD)。

一、测试代码:

 #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%d.h", 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%d.h\"\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%d.h\"\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;
}

现在,我的各种测试运行:

 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 只有一个版本稍快, 差异可以忽略不计,并且远远超过实际构建和链接代码所需的时间。也许如果代码库足够大,它实际上可能会导致构建时间相差几秒钟,但是现代编译器能够优化 #ifndef 守卫,操作系统具有良好的磁盘缓存这一事实,以及不断增加的存储技术的速度,似乎性能争论是没有实际意义的,至少在当今时代的典型开发人员系统上是这样。较旧和更奇特的构建环境(例如,托管在网络共享上的标头,从磁带构建等)可能会稍微改变等式,但在这种情况下,首先简单地创建一个不那么脆弱的构建环境似乎更有用。

事实是, #ifndef 是标准化的标准行为,而 #pragma once 不是,而 #pragma once #ifndef 也处理奇怪的文件系统和搜索路径角落情况 --- 可能会对某些事情感到非常困惑,导致程序员无法控制的错误行为。 #ifndef 的主要问题是程序员为他们的守卫选择了坏名字(有名字冲突等等),即使这样,API 的使用者也很有可能使用 #undef 来覆盖这些坏名字 --- 也许不是一个完美的解决方案,但它是 可能 的,而 #pragma once 如果编译器错误地剔除一个 #include --- 则没有追索权。

因此, 即使 #pragma once 明显(稍微)更快,我不同意这本身就是在 #ifndef 警卫上使用它的理由。

增加头文件的数量并将测试更改为仅运行预处理器步骤消除了编译和链接过程添加的任何少量时间(这在以前是微不足道的,现在不存在)。正如预期的那样,差异大致相同。

原文由 fluffy 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
logo
Stack Overflow 翻译
子站问答
访问
宣传栏