头文件保护
头文件保护(Include Guard)防止同一个头文件被多次包含导致的重复定义错误。当多个头文件互相包含、或一个源文件直接和间接包含同一头文件时,没有保护的头文件会导致编译错误。
问题场景
/* point.h */
struct Point { int x, y; };
/* shape.h */
#include "point.h"
/* main.c */
#include "point.h" /* 直接包含 */
#include "shape.h" /* 间接包含 point.h */
/* 结果:point.h 的内容被包含两次,struct Point 重复定义 */
标准保护写法
使用 #ifndef / #define / #endif:
/* point.h */
#ifndef POINT_H
#define POINT_H
struct Point { int x, y; };
typedef struct Point Point;
Point create_point(int x, int y);
double point_distance(Point a, Point b);
#endif /* POINT_H */
工作原理:
- 第一次包含:
POINT_H未定义,执行#define POINT_H和后续内容 - 第二次包含:
POINT_H已定义,跳过#ifndef和#endif之间的内容
命名约定
保护宏的命名应唯一,通常基于文件名:
/* math_utils.h */
#ifndef MATH_UTILS_H
#define MATH_UTILS_H
/* ... */
#endif
/* 复杂项目:加项目前缀 */
#ifndef MYPROJECT_MATH_UTILS_H
#define MYPROJECT_MATH_UTILS_H
/* ... */
#endif
避免使用以下划线开头的名字(保留给实现):
#ifndef _POINT_H /* 不推荐:以下划线开头 */
#define _POINT_H
/* ... */
#endif
#pragma once
某些编译器支持 #pragma once,功能与头文件保护相同:
/* point.h */
#pragma once
struct Point { int x, y; };
#pragma once 更简洁,但不是标准 C(虽然几乎所有现代编译器都支持)。如果追求严格标准兼容性,使用 #ifndef 保护;如果允许编译器扩展,#pragma once 更方便。
混合使用
可以同时使用两种保护(冗余但安全):
/* point.h */
#ifndef POINT_H
#define POINT_H
#pragma once
struct Point { int x, y; };
#endif /* POINT_H */
头文件保护的位置
保护应包裹头文件的全部内容:
/* 正确 */
#ifndef POINT_H
#define POINT_H
/* 所有内容 */
#endif
/* 错误:保护不包裹全部内容 */
#ifndef POINT_H
#define POINT_H
#endif
/* 这部分没有保护! */
struct Point { int x, y; };
常见错误
忘记保护:
/* unprotected.h */
struct Point { int x, y; }; /* 如果被包含两次,重复定义 */
宏名冲突:
/* point.h */
#ifndef UTILS_H /* 错误:与 utils.h 的宏名冲突 */
#define UTILS_H
struct Point { int x, y; };
#endif
保护宏名与内容相关:
/* point.h */
#ifndef POINT /* 错误:POINT 可能是合法标识符 */
#define POINT
struct Point { int x, y; }; /* 如果代码中有 #undef POINT,保护失效 */
#endif
最佳实践
- 每个头文件都必须有保护
- 使用
#ifndef/#define/#endif(标准兼容) - 宏名基于文件名,全大写,用下划线替换点
- 或
#pragma once(如果允许编译器扩展) - 保护包裹头文件的全部内容
- 避免宏名冲突(加项目前缀)