#pragma once的位置是在#include之前还是之后?

32

在现有的代码中,我看到#pragma once被用在头文件#include之后。

//Some_Header.h
#include "header1.h"
#include "header2.h"

#pragma once

//implementations

与其

//Some_Header.h
#pragma once

#include "header1.h"
#include "header2.h"

//implementations

我原以为它必须像第二个示例那样,但是你在文件中定义#pragma once是否有关系,或者预处理器可以在任何地方找到它? 编辑 我知道#pragma once不是标准的一部分,包含保护是标准的一部分,但这不是我的问题。

2
请注意,尽管许多编译器广泛支持此功能,但它并不是标准C++的一部分。 - Daniel Kamil Kozar
2
我知道很多关于这个在不支持的地方无法找到我想要的解释的文章。 - turoni
3
我从没用过它,所以我不能确定 - 但是我会尽可能把它放在文件的开头。这样更合理。 - Daniel Kamil Kozar
3
针对重复标记的问题: 另一个问题涉及到#pragma once位置和#include防护的问题。 而这个问题则是关于#pragma once和头文件包含的问题。 所以我认为它被错误地标记了。 - turoni
2
这不是一个重复的问题,因此不应该被标记为重复。这些问题在各个层面上都没有关联。 - Pablo Ariel
显示剩余3条评论
3个回答

33

@user17732522 所有的 #pragma 均为实现定义。 - Caleth
@Caleth 我之前认为 #pragma 后第一个标记的宏替换不应该发生,因为即使将 STDC 定义为宏,#pragma STDC 仍应该有效,但实际上这并不重要,因为 C++ 并没有定义 #pragma STDC,而 C 明确规定在替换之前应该先识别出 STDC。所以,似乎不存在关于符合标准的任何问题。然而,这仍然似乎是 MSVC 特有的行为。GCC 和 Clang 似乎甚至能够识别 #pragma once 即使 once 被定义为宏。 - user17732522

9

这个问题并没有一个完整的答案涵盖了大三编译器,所以这里是我尝试更完整的回答。

概括

  • 简而言之:如果你关心可移植性,请将其放在可能与之冲突的任何#include#define语句之前(例如,将其放在头文件中的第一位)。
  • 被所有主要编译器支持(“大三”x86_64编译器以及英特尔和嵌入式编译器)
  • 位置通常不重要,只要它到达预处理器即可(例如,不被#if-分支阻塞)
  • 不同的编译器对于是否应该首先使用它有不同的看法,但没有记录下未如此做会发生什么。
  • 大多数编译器已经检测到包含保护并将其视为pragma once,使得好处主要是不创建唯一的保护名称。

以下是一个快速的摘要指南:

编译器 支持情况 文档
Clang 支持 兼容GNU。未记录,但代码显示它作为普通预处理
GCC 支持 gcc指示符
MSVC 支持(1) MSVC one 指示符
Intel (ICC) 支持(1) Intel编译器参考手册 - 支持的指示符
Intel (ICL) 支持(1) ICL使用MSVC前端
Intel (ICX) 支持 ICX基于Clang
Texas Instruments 支持(2) 参考手册 5.11.23
Texas Instruments (Clang) 支持 这是Clang的一个分支,所有主要功能仍然有效
ArmCC 支持(3) 编译器文档 #pragma once

(1) - 支持,但受宏扩展的影响。

(2) - 支持,但文件头部应该放置该指令。

(3) - 支持,但不建议使用。

细节

GCC

来自GCC参考手册:

如果在扫描头文件时遇到#pragma once,那么该文件将永远不会再次被读取,无论如何。这是防止头文件被多次包含的‘#ifndef’的一种不太便携的替代方法。

(重点标注为我的)

扫描是在预处理期间进行的,只要#pragma语句可见于预处理器(不在从#if开始的不可达条件块中),则它将生效。

GCC的#pragma once不受预处理器替换的影响。

在线示例

Clang

Clang的参考手册似乎并没有明确指定#pragma once,但据我所知,Clang应该与大多数(如果不是全部)GCC内置函数和特性兼容。查看Clang预处理阶段的源代码表明了你所期望的情况;它在预处理期间处理#pragma once。(source)
 void Preprocessor::HandlePragmaOnce(Token &OnceTok) {
   ...
   // Mark the file as a once-only file now.
   HeaderInfo.MarkFileIncludeOnce(getCurrentFileLexer()->getFileEntry());
 }

和GCC一样,#pragma once的放置位置不重要,只要它是第一个,并且不受预处理器替换影响。

示例

MSVC

MSVC的#pragma once文档没有说明它应该放在哪里,只是说它应该在源文件中(并提供了一个在顶部使用它的示例)。

正如其他人所提到的,当在MSVC中使用#pragma once时,它会受到预处理器扩展的影响。

带有替换

示例

不带替换

示例

Intel (CL-Based)

在Windows上使用Intel编译器时,编译器会使用MSVC兼容模式(ICL)。虽然它没有在支持的Pragma中记录,但似乎是被支持的。只要预处理器能够到达,放置位置似乎也不重要。
ICL的#pragma once存在与MSVC相同的预处理器扩展问题。

注意:icl不受编译器探索器支持,因此没有示例可用。

Intel(基于GNU)

在Linux或旧版macOS版本(ICC)上使用英特尔编译器时,编译器使用GNU兼容模式。

与上面一样,它没有明确列为支持的编译指示, 但实际上似乎是被支持的。只要预处理器到达它,放置位置似乎也不重要。

ICC的#pragma once存在预处理器扩展问题,这与MSVC的经验相同。

使用替换

在线示例

不使用替换

在线示例

英特尔(基于Clang)

新的英特尔ICX NextGen编译器基于Clang / LLVM技术。在行为上,与Clang相匹配。

与其他英特尔编译器不同,但与Clang一样,这不会遭受预处理器扩展问题。

实时示例

Arm(armcc

armcc编译器建议不要使用#pragma once, 但也提供了一个示例,它存在于#define语句之后作为可选功能与包含保护一起使用。

根据示例,放置位置可能不是问题。

不清楚这是否会经历任何预处理器扩展。


注意: 在编译器浏览器上不支持armcc,因此没有示例可用。

Texas Instruments(TI ArmCL)

参考手册第5.11.23节所述:

This pragma should be used at the beginning of a header file that should only be included once. For example:

// hdr.h
#pragma once
#warn You will only see this message one time
struct foo
{
  int member;
};

(我的强调)

我没有测试过如果它被移动到注释头以下会发生什么,但编译器只 正式 支持在文件 开头 使用。

我认为这应该不重要,但无法确认。

不清楚这是否会经历任何预处理扩展。


注意: tiarmcl(以及其他类似的ti编译器)不受编译器探索器支持,因此没有示例可用。

Texas Instruments(tiarmclang

这是clang的一个分支,因此它的行为与clang相同。

在此实现中,#pragma once可以有效地放置在预处理器到达的任何位置,并且不涉及预处理器替换。


注意:编译器探索器不支持tiarmclang,因此没有示例可用。


非常棒的研究工作,谢谢! - jp48

7

#pragma once仅与其所在的文件相关。编译器会检查文件中是否包含该指示符,而其位置并不重要。因此,#pragma once所在的行可以放置在文件的任何位置,除了由条件预处理指令(例如#if#ifdef#ifndef)排除编译的代码块之外。被排除的代码块不会被编译器解析,如果它包含预处理指令,则没有任何效果。

尽管#pragma once可以放置在编译器解析的任何行,但我强烈建议遵循通常的做法,将#pragma once放在头文件的开头。

另外,正如@user7860670提到的,对于MSVC编译器来说,#pragma指示符的参数会受到宏展开的影响。但是,gcc和clang都不支持它:


1
“文件是否包含此#pragma指示对编译器很重要,而其位置并不重要。”这并不完全正确。实际上,编译器必须确实到达#pragma once指令。如果它隐藏在未被执行的#if分支后面,则不会生效:https://godbolt.org/z/j3jecEd6v - Human-Compiler
你是对的。如果代码块被排除在编译之外,它不会被编译器解析,如果该块包含预处理器指令,则没有任何效果。我已经更新了答案,感谢您的输入。 - Egor

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接