头文件重复包含怎么办?
更新时间:2026-09-20。问题驱动短答页。
#include的本质是预处理期文本替换——把头文件内容原样贴进当前文件。同一头文件经多条路径被贴两次,里面的类型/变量定义就会重复,触发redefinition错误。基础见 头文件与 #include。
一句话回答
给每个头文件加「包含守卫」:传统写法 #ifndef X_H / #define X_H / … #endif,或现代编译器通用的 #pragma once;二者都保证同一翻译单元内头文件只被展开一次。
写法一:#ifndef 守卫(可移植、最通用)
// calc.h
#ifndef CALC_H // 没定义过才进入
#define CALC_H
int add(int a, int b);
struct Point { int x; int y; };
#endif // CALC_H第一次包含:CALC_H 未定义 → 进入并定义;第二次包含:已定义 → 整段跳过。宏名要唯一,惯例用 项目_路径_文件名_H。
写法二:#pragma once(简洁,主流编译器都支持)
// calc.h
#pragma once
int add(int a, int b);
struct Point { int x; int y; };GCC/Clang/MSVC 均支持,靠文件(inode/路径)唯一性去重,不用想宏名、也不会宏名撞车;代价是标准 C/C++ 未规定(但事实通用)。很多项目两者都写或择一。
为什么没守卫会炸
a.h 定义 struct Point
b.h #include "a.h"
main.c 同时 #include "a.h" 和 "b.h"
→ a.h 被贴两次 → struct Point 出现两次 → error: redefinition of 'struct Point'即使现在没直接重复,头文件依赖关系一复杂就极易间接重复,所以新建头文件第一行就写守卫是标配。
关键:声明放 .h,定义放 .c
守卫防的是「同一 .c(翻译单元)内重复展开」,但它不能让同一个定义合法地出现在多个 .c 里:
- 头文件放:函数/变量的声明(
extern int g;)、struct/typedef/宏/inline。 - 源文件放:函数体、全局变量定义(
int g = 1;)。
在 .h 里写 int g = 1; 又被两个 .c 包含,守卫拦不住跨文件重复——链接期报 multiple definition。详见多文件工程与头文件设计。
常见坑
- 宏名不唯一:两个头文件都叫
COMMON_H,会导致其中一个被误跳过;用带项目/路径前缀的名字。 - 守卫写反/漏 #endif:整片代码被异常屏蔽,注意配对。
- 把头文件当命名空间:include 只是文本展开,没有隔离;防重靠守卫 + 声明/定义分离。
- 循环包含(a.h 含 b.h、b.h 含 a.h):守卫会让其中一方看到「不完整类型」,根因是设计耦合,用**前置声明(forward declaration)**打断,而非堆守卫。
#pragma once用在硬链接/拷贝的同名文件:个别极端场景判定不一致,追求极致可移植用 #ifndef。- 守卫宏与代码里宏撞名:守卫名全大写且足够特殊。
总结
#include 是预处理期文本替换,头文件经多条路径被包含两次就会造成 struct/变量 redefinition;每个头文件都要加包含守卫——可移植的 #ifndef/#define/#endif 或主流编译器通用的 #pragma once——保证同一翻译单元只展开一次;但守卫只防单文件内重复,函数体和全局变量定义必须放 .c、头文件只放声明与类型宏,否则跨翻译单元仍会 multiple definition,循环包含则应以前置声明解耦而不是靠守卫硬挡。