#ifndef #define #endif
作用与原理
-
防止重复包含
当多个源文件包含同一个头文件,或头文件之间相互嵌套时,可能导致头文件内容被多次编译。使用这三条指令可以确保头文件内容仅被处理一次。 -
实现机制
-
#ifndef
(if not defined):检查某个标识符是否未定义。 -
#define
:若标识符未定义,则定义它。 -
#endif
:结束条件编译块。
当首次包含头文件时,标识符未定义,预处理器会处理头文件内容并定义标识符。后续再次包含时,因标识符已定义,内容被跳过。
-
#ifndef XXXX
#define XXXX
#endif
字面意思
- 如果宏
XXXX
未定义,则执行#ifndef XXXX
和#endif
之间的代码(包括#define XXXX
)。 - 如果宏
XXXX
已定义,则跳过#ifndef XXXX
和#endif
之间的代码。
示例 不用#ifndef #define #endif
a.h
#include"b.h"
#define A 1
b.h
#include "a.h"
#define B 2
main.cpp
#include<iostream>
#include"a.h"
#include"b.h"
int main()
{
return 0;
}
编译运行 会报错
In file included from b.h:1:0,
from a.h:1,
from b.h:1,
......
from b.h:1,
from a.h:1,
from main.cpp:2:
a.h:1:14: error: #include nested too deeply
#include"b.h"
^
In file included from a.h:1:0,
from b.h:1,
from a.h:1,
.........
from b.h:1,
from a.h:1,
from b.h:1,
from main.cpp:3:
b.h:1:14: error: #include nested too deeply
#include"a.h"
分析原因
main.cpp中包含了两个头文件a.h 和b.h
a.h中包含了头文件b.h
b.h中包含了头文件a.h
编译器预编译时候 就会形成一个死循环 a.h和b.h在一直循环引用
造成nested too deeply(嵌套太深)
示例 用#ifndef #define #endif
修改头文件
a.h
#ifndef __A_H_
#define __A_H_
#include"b.h"
#define A 1
#endif
b.h
#ifndef __B_H_
#define __B_H_
#include"a.h"
#define B 1
#endif
main.cpp
#include<iostream>
#include"a.h"
#include"b.h"
int main()
{
return 0;
}
编译运行通过
原因分析
1 字面意思 #ifndef 如果没有定义后面的宏,执行2 。如果#ifndef 如果有定义后面的宏,执行3
2 #define 执行3
3 #endif
备注:#define 标识 应该是唯一的, 一般以头文件名字大写加上下划线和大写的H构成。
一般头文件不要定义变量,只做声明(重复引用就会有重定义)
指令 #ifdef 和 #else #endif
#include<iostram>
#define TEST_CODE
int main()
{
int a 10;
#ifdef TEST_CODE
a =10;
#else
a = 100;
#endif
return 0;
}
#pragma once
核心作用
#pragma once
是 C/C++ 中的预处理指令,用于确保头文件在同一个编译单元(如 .cpp
文件)中仅被包含一次。它能防止因重复包含同一头文件导致的编译错误(如重复定义、类型冲突等)。
实现原理
-
编译器依赖
#pragma once
由编译器直接处理(而非预处理器),通过记录头文件的物理路径或唯一标识符来检测重复包含。
例如,若header.h
被多次#include
,编译器会在首次包含后标记该文件,后续包含时直接跳。 -
文件唯一性判断
编译器通过文件路径或哈希值判断头文件是否已加载。若路径相同(如绝对路径一致),则视为同一文件。
例外情况:通过符号链接或不同相对路径引用同一文件时,可能失效。
代码示例与实现
-
基本用法
在头文件顶部添加#pragma once
:// header.h #pragma once void exampleFunction();
#pragma once 和#ifndef...#define...#endif
差异比较
特性 | #pragma once | #ifndef /#define 宏保护 |
---|---|---|
实现方式 | 编译器指令,基于文件路径唯一性 | 预处理器宏,依赖唯一宏名 |
代码简洁性 | 单行指令,无需手动命名 | 需定义宏名,代码冗 |
编译效率 | 某些编译器可优化(直接跳过文件 | 需预处理器逐行判断宏是否定 |
兼容性 | 主流编译器支持(MSVC、GCC≥3.4、Clang) | 符合 C/C++ 标准,通用性 |
注意事项
-
编译器兼容性
现代编译器(如 MSVC、GCC≥3.4、Clang)均支持#pragma once
,但极少数旧版或特殊环境可能不支持。
跨平台项目建议优先使用宏保护或混合方案。 -
文件系统依赖
若同一文件通过不同路径包含(如符号链接或拷贝文件),#pragma once
可能失效,此时宏保护更可靠
2.1 符号链接导致 #pragma once 失效
头文件 original.h 通过符号链接 link.h 被包含。
代码中分别包含 #include "original.h" 和 #include "link.h"。
结果:编译器可能认为这是两个不同文件,导致 original.h 被重复包含,引发编译错误。
2.2 宏保护可靠性验证
无论通过何种路径包含 original.h 或 link.h,只要头文件内定义相同宏名(如 MY_HEADER_H),预处理器仅允许一次展开。
结果:头文件内容仅被包含一次,避免重复定义问题。
技术总结
#pragma once 的局限性
编译器可能无法识别同一文件的多个路径别名,导致保护失效。
适用于文件路径唯一性明确的场景(如无符号链接、固定相对路径)。
#ifndef 的优势
完全依赖宏名逻辑,与文件系统无关,可靠性更高。
跨平台兼容性强,尤其适合需要处理符号链接或动态路径的项目
使用建议
-
优先选择
#pragma once
- 适用于现代项目,尤其是基于主流编译器(如 VS、GCC、Clang)的环境。
- 简化代码维护,减少宏命名冲突风险。
-
需兼容老旧编译器时选择
#ifndef
- 若需支持不支持
#pragma once
的旧编译器(如早期嵌入式编译器)。 - 跨平台项目中统一使用
#ifndef
可避免潜在兼容问题。
- 若需支持不支持
-
避免混合使用
- 同时使用两种方式可能引发宏定义与编译器行为的冲突,增加调试难度。