应优先用#pragma once,当且仅当项目仅面向Clang、GCC≥5.0、MSVC等现代编译器,且无硬链接、软链接或不同路径包含风险;否则必须用传统卫语句。

#include 重复包含会导致编译错误或符号重定义,最直接有效的办法是用 #pragma once 或传统卫语句(include guard)——但二者不是等价替代,#pragma once 更简洁,却有兼容性和路径敏感问题。
什么时候该用 #pragma once 而不是 include guard?
当项目只面向主流现代编译器(Clang、GCC ≥ 5.0、MSVC),且头文件不会被硬链接、符号链接或通过不同路径多次引入时,#pragma once 是更轻量的选择。它写法简单、不易出错,编译器能更快跳过已处理的头文件。
- 适合内部模块头文件、构建系统可控的代码库
- 不适用于需要支持 GCC 4.x 或嵌入式工具链(如某些 ARM GCC 变种)的场景
- 若头文件被软链接到多个目录(例如
src/common.h和build/include/common.h指向同一文件),部分旧版 Clang 或 MinGW 可能无法识别为同一物理文件,导致重复包含
#pragma once 和传统 #ifndef XXX_H 卫语句的区别在哪?
核心区别在识别机制:#pragma once 依赖编译器对文件物理路径的哈希或 inode 判断;而卫语句靠预处理器宏名唯一性,与路径无关,但需人工保证宏名不冲突。
-
#pragma once不受宏污染影响,也不怕忘记#endif—— 语法上无配对要求 - 卫语句可跨平台无条件生效,但容易因命名随意(比如
#ifndef UTIL_H)引发宏名冲突,尤其在大型第三方集成中 - 某些 IDE(如早期 VS Code C/C++ 插件)仅靠
#pragma once推导头文件依赖,可能漏掉宏定义触发的条件编译分支
哪些情况必须避免 #pragma once?
以下场景下它不可靠,应强制使用卫语句:
立即学习“C++免费学习笔记(深入)”;
- 头文件通过不同绝对/相对路径被
#include(例如#include "core/log.h"和#include "../src/core/log.h") - 构建系统生成头文件并软链接进多个 build 目录(CI 环境常见)
- 目标平台编译器明确不支持(如 TI C6000 DSP 编译器、某些 AVR-GCC 衍生版)
- 代码要作为 SDK 对外分发,且需保证最低 GCC 4.8 兼容性
推荐的混合写法(兼顾安全与简洁)
不少团队采用“双保险”:先写 #pragma once,再加一层卫语句。编译器优先走 #pragma once 快路,退化时仍能兜底。
#pragma once #ifndef MYLIB_STRING_UTILS_H_ #define MYLIB_STRING_UTILS_H_ #includenamespace mylib { std::string trim(const std::string& s); } // namespace mylib #endif // MYLIB_STRING_UTILS_H_
注意 MYLIB_STRING_UTILS_H_ 宏名应含项目前缀+文件名+下划线,避免和其它库冲突。这种写法在大型工程中被 LLVM、Chromium 等项目实际采用,不是过度设计。
真正麻烦的从来不是选哪一种,而是整个项目混用两种方式又缺乏规范——头文件边界一旦模糊,模板实例化、ODR 违反、静态变量多份副本等问题就会在链接期或运行时突然冒出来。











