1 介绍

在 C/C++ 开发中,#pragma once#ifndef CONFIG_H ... #endif(通常称为“头文件守卫”或 Include Guards)都用于防止头文件被重复包含,从而避免类、结构体或全局变量被重复定义而引发编译错误。

2 用法

#ifndef CONFIG_H ... #endif的用法:

#ifndef CONFIG_H
#define CONFIG_H

//内容

#endif // CONFIG_H

#pragma once用法:

#pragma once
//内容

3 区别

尽管它们的目的相同,但在实现原理、兼容性以及潜在风险上存在显著差异:

比较维度 #pragma once #ifndef CONFIG_H ... #endif
标准与兼容性 非标准扩展。但几乎所有现代主流编译器(GCC, Clang, MSVC)均广泛支持。极少数老旧或非主流编译器可能不识别。 C/C++ 语言标准。任何符合标准的预处理器都能正确处理,具有最高的跨平台可移植性。
底层机制 基于物理文件标识。编译器通过文件的绝对路径或 inode 来判断是否已处理过该文件。 基于宏名称判断。预处理器检查符号表中是否存在 CONFIG_H 这个宏,以此决定是否跳过内容。
代码简洁度 极高。只需在文件开头写一行指令,无需手动管理宏名,也不会污染全局命名空间。 较低。需要在文件首尾分别添加三行代码,且必须保证宏名的唯一性。
潜在缺陷 若同一物理文件通过不同的路径(如符号链接、硬链接)被引用,某些编译器可能无法正确识别为同一文件,导致保护失效。 若不同头文件不小心使用了相同的宏名(如都叫 CONFIG_H),会导致宏冲突,使得其中一个头文件的内容被意外屏蔽。
编译效率 较高。编译器在打开文件前就能通过文件系统信息直接跳过,避免了重复的 I/O 操作和宏解析。 略低。预处理器每次都需要打开文件并扫描到对应的 #endif 才能确定是否需要跳过。
使用场景建议 如果你的项目主要在 Visual Studio、Clang 等现代 IDE/编译器环境下开发,且不需要兼容非常古老的系统,强烈建议使用 #pragma once。它更简洁、不易出错,且在大型项目中能带来微小的编译速度提升。 如果你正在编写需要高度跨平台、跨编译器支持的开源库,或者你的目标环境中包含不支持该扩展的老旧编译器,传统的宏守卫是唯一稳妥的选择。

4 二者混用

可能会在一些代码中看到两者结合使用的情况:

#pragma once
#ifndef CONFIG_H
#define CONFIG_H
// 头文件内容
#endif

实际上,这种做法没有任何额外优势。现代编译器对标准的 #ifndef 宏守卫同样会进行多包含优化(Multiple Include Optimization),其内部处理方式与 #pragma once 基本一致。混用不仅冗余,还可能因为引入了宏定义而增加维护成本,因此不建议刻意将两者叠加使用。

更多推荐