#pragma once与#ifndef两种防止头文件二次编译的区别

本文对比分析了C语言中使用#ifndef #define #endif宏定义和C++中使用#pragma once防止头文件二次编译的方法,探讨了各自的优缺点及适用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转自:http://blog.youkuaiyun.com/zlhy_/article/details/8192580


在最开始接触到C语言的时候,看到很多大牛们写的代码里面就有#ifndef  #define #endif 这三条语句,当时不明白什么意思,慢慢的后来自己也开始用了,也知道了他们合起来使用时为了防止头文件被二次编译,因为我们曾经写代码的过程中一定遇到过<.......重复定义或是重复包含>等类似的错误吧。

到了学习C++的时候,我们老师给我们看他的代码的时候发现他的头文件里用的是#pragma once,就奇怪这是什么呢?然后私下百度了后才知道,这也是防止头文件二次编译的,然后就开始用#pragma once了,这一用就是一个学期了,后来发现用着很好,再也没有重复编译或是重复包含的错误了,当时写C++代码的时候习惯了单个类放在独立的头文件里,这样子好纠错,也好扩展,调理更清晰。

最近一段时间为了给学弟学妹们讲C语言的知识,又回过头来看宏定义的方法。就想写这么一篇博客来说明一下这两者之间的区别,过程中也百度了很多,也看了很多别人是怎么写的,怎么认识的。这里面肯定有他人文章中的影子,但是这绝对不是简单的复制粘贴,而是自己看了那么多之后的思考,总结之作。


#ifndef这种是最早期使用的方法,是基于语言的宏定义名字不能冲突的前提下的。这种方法不仅能保证同一个头文件不会被包含两次,也可以保证内容完全相同的两个文件也只能被包含一次。但是他优缺点,就是你的#ifndef 后面跟的宏名字和你程序中的其他宏名字发生了”撞车“,那么会出现以下两种后果

第一种:file1头文件中有一个宏

//file1.h

#define BOOK_H   //宏名

现在又有一个文件 book.h 里面使用了宏定义方式防止头文件二次编译

#ifndef BOOK_H

#define BOOK_H

//  program codes

#endif

下面是你的主函数所在文件内容

#include"fil1.h"

#include"book.h"   //这两个都是你自己的头文件

#include<........>

..................

预编译阶段把file1文件展开,就得到了宏 BOOK_H,在处理book.h文件时就发现BOOK_H这个宏已经存在了,就不会包含book.h头文件了,这就是弊端所在了。

第二种就是:

两者顺序反过来了,弊端类似。


#pragma once这种方式,是微软编译器独有的,也是后来才有的,所以知道的人并不是很多,用的人也不是很多,因为他不支持跨平台。如果你想写跨平台的代码,最好使用上一种。这是一种由编译器提供支持的方式,防止同一文件的二次编译,这里的同一文件指的是物理文件。

他也是有弊端的:

假如你的某一个头文件有多份拷贝,那么这些文件虽然在逻辑上都是一样的,但是在物理上他们却是不同的,所以当你把这些文件包含的时候,就会发现真的都包含进来了,然后就是编译错误了。还有,当物理上的同一文件被嵌套包含的时候,使用第一种方法预处理会每一次打开该文件做判断的,但是第二种方法则不会,所以在此#pragma once 会更快些。下面举例说明

    // Test1.h
    #ifndefine  TEST1_H
    #defineTEST1_H
    ...
    #endif
    
    // Test2.h
    #pragma once        
    ...
    
    // Test.cpp
    #include "Test1.h"      // line 1
    #include "Test1.h"      // line 2
    #include "Test2.h"      // line 3
    #include "Test2.h"      // line 4 这里的Test2.h是同一物理文件

预处理器在执行这四句的时候,先打开Test1.h然后发现里面的宏TEST1_H没有被定义,所以会包含这个文件,第二句的时候,同样还是会打开Test2.h的发现宏已定义,就不包含该文件按了。第三句时,发现之前没有包含Test2,h则会把该文件包含进来,执行第四句的时候,发现该文件已经被包含了,所以不用打开就直接跳过了。


<think>我们正在讨论C++中两种防止头文件重复包含的预处理指令:`#pragma once`和 `#ifndef`(通常`#define`和`#endif`一起使用)。根据提供的引用,我将整理它们的区别。###1.标准性-**`#ifndef`**:是C/C++标准的预处理指令,被所有符合标准的编译器支持[^2][^3]。 -**`#pragmaonce`**:是非标准指令,依赖于编译器支持。虽然大多数现代编译器(如GCC、Clang、MSVC)都支持,但在一些老旧的编译器上可能不被支持[^2][^3]。 ###2.实现原理-**`#ifndef`**(宏守卫):```cpp#ifndef HEADER_FILE_H#define HEADER_FILE_H//头文件内容#endif```原理:检查宏`HEADER_FILE_H`是否已定义。若未定义,则定义该宏并包含头文件内容;若已定义,则跳过内容。它依赖于程序员定义的唯一宏名[^1][^2]。- **`#pragma once`**:```cpp#pragmaonce//头文件内容```原理:由编译器保证同一个物理文件(根据文件系统路径唯一标识)不被重复包含[^2][^5]。 ###3.作用范围- **`#ifndef`**:-可防止同一头文件被多次包含。-也可防止内容完全相同的两个不同头文件被同时包含(因为宏名相同会导致跳过)[^2][^4]。 -**`#pragmaonce`**:-仅防止同一个物理文件被多次包含。-无法防止内容相同但路径不同的两个头文件被重复包含(例如,文件拷贝到不同目录)[^2][^5]。###4.优缺点对比|特性| `#ifndef`(宏守卫)| `#pragmaonce`||---------------------|---------------------------------------|----------------------------------------|| **兼容性**| ✅所有C/C++编译器支持|❌部分老旧编译器不支持(如GCC<3.4)[^3]| |**可读性/简洁性**|❌需手动定义唯一宏名,代码较冗长|✅一行指令,简洁直观|| **处理速度**|⚠️每次包含都需检查宏定义(稍慢)|✅编译器直接记录文件路径(更快)[^2]| |**防重复能力**| ✅可防内容相同的不同文件|❌仅防同一物理文件|| **宏名冲突风险**|❌宏名可能冲突(需确保唯一性)| ✅无宏名冲突问题| ###5.实际使用建议1. **兼容性优先**:若需支持老旧编译器(如嵌入式开发),使用`#ifndef`[^3]。 2. **现代项目**:多数现代项目(如Qt、LLVM)已转向`#pragma once`,因其简洁且编译更快[^2]。 3. **混合使用**:不推荐同时使用两者(如`#pragma once`+`#ifndef`),因为:-不能避免宏冲突问题[^4]。-可能增加编译错误风险(如老旧编译器遇到`#pragma once`报错)。###示例对比```cpp//使用 #ifndef#ifndefMY_HEADER_H#defineMY_HEADER_Hstruct Data{int x; };#endif``` ```cpp//使用 #pragma once#pragmaoncestruct Data{ intx;}; ```###总结- **`#ifndef`**:兼容性强,但依赖唯一宏名且速度稍慢。 -**`#pragmaonce`**:简洁高效,但受限于编译器支持和物理文件路径。 随着编译器迭代,`#pragmaonce`已成为主流选择。在C++20模块(Modules)普及后,两者可能逐渐被取代[^2]。 ---§§相关问题§§1.C++20模块(Modules)如何替代传统头文件机制?2.如何为大型C++项目设计避免命名冲突的头文件结构?3.预处理器指令在编译过程中的具体执行顺序是怎样的?4.为什么C++标准委员会未将`#pragmaonce`纳入标准?[^1]:引用[1]:`#ifndef`通过宏定义实现重复包含检查。[^2]:引用[2][5]:`#pragma once`由编译器保证同一物理文件不被重复包含。[^3]:引用[3]:`#ifndef`受语言天生支持,兼容性更好。 [^4]:引用[4]:混合使用两种方法可能带来混淆。[^5]:引用[5]:`#pragma once`避免了宏名冲突问题。</think>### C++ 中 `#pragma once` `#ifndef` 的区别详解 #### 1. **标准性** - **`#ifndef`** 是 C++ 标准支持的预处理指令,所有编译器必须支持[^3][^4]: ```cpp #ifndef HEADER_H #define HEADER_H // 头文件内容 #endif ``` - **`#pragma once`** 非标准指令,依赖编译器实现(GCC/Clang/MSVC 支持,但老旧编译器可能不支持)[^2][^3]: ```cpp #pragma once // 头文件内容 ``` #### 2. **实现原理** | 特性 | `#ifndef` | `#pragma once` | |--------------------|------------------------------------|------------------------------------| | **检查依据** | 宏名称(如 `HEADER_H`) | 文件的物理路径(操作系统级标识) | | **重复包含防护** | 防止相同宏名的文件重复包含 | 仅防止同一物理文件重复包含[^5] | | **内容相同不同文件**| ✅ 能防护(宏名相同时) | ❌ 无法防护[^2][^5] | #### 3. **性能差异** - **编译速度** `#pragma once` 通常更快,因为编译器直接通过文件系统路径缓存包含状态,无需解析宏定义[^2]。 - **宏处理开销** `#ifndef` 需要预处理器处理宏定义和条件判断,在大型项目中可能产生可测量的开销。 #### 4. **使用风险** - **`#ifndef` 的宏冲突** 不同头文件使用相同宏名会导致防护失效: ```cpp // file1.h #ifndef COMMON_H // 其他文件冲突 #define COMMON_H // ... #endif ``` - **`#pragma once` 的路径陷阱** 符号链接或相同内容的不同路径文件会被视为不同文件: ```bash src/ ├── header.h └── utils/ └── header.h # 符号链接到上级 ``` 此时包含两个路径会重复包含[^5]。 #### 5. **兼容性实践建议** | 场景 | 推荐方案 | 原因 | |--------------------|------------------------|-----------------------------------| | 跨平台/老旧编译器 | `#ifndef` | 标准支持,100% 兼容[^3][^4] | | 现代项目 | `#pragma once` | 简洁、编译更快[^2] | | 关键库开发 | 二者同时使用 | 双重保险(但可能增加混淆)[^4] | #### 实际代码示例 ```cpp // 推荐现代用法 (GCC/Clang/MSVC) #pragma once #include <vector> // 兼容性用法 (所有编译器) #ifndef LEGACY_HEADER_H #define LEGACY_HEADER_H #include <string> #endif ``` ### 总结对比表 | 特性 | `#ifndef` | `#pragma once` | |--------------|----------------------------|---------------------------| | 标准性 | ✅ C++ 标准 | ❌ 编译器扩展 | | 防护范围 | 逻辑内容(宏名相同文件) | 物理文件(相同路径文件) | | 编译性能 | 较慢 | 较快[^2] | | 宏冲突风险 | 高 | 无 | | 多副本防护 | ✅ | ❌ | | 代码简洁性 | 冗长(需3行) | 简洁(1行) | > **最佳实践**:现代项目优先用 `#pragma once`,需兼容老旧环境时用 `#ifndef`。避免混用二者,除非有特殊兼容需求[^4]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值