预处理是 C 编译流程的第一步,负责宏展开、条件编译和文件包含。掌握预处理技术可以实现代码复用、跨平台兼容与调试开关。本文详细介绍预处理指令的用法与技巧。
一、预处理流程概述
C 代码编译分为四个阶段:预处理、编译、汇编、链接。预处理阶段完成以下工作:
- 展开宏定义(#define)
- 处理文件包含(#include)
- 条件编译(#if、#ifdef、#ifndef)
- 处理特殊指令(#pragma、#error)
/* 查看预处理后的代码 */ $ gcc -E main.c -o main.i /* 只预处理,不编译 */ $ cpp main.c > main.i /* 手动调用预处理器 */
二、宏定义详解
1. 简单宏
/* 符号常量 */ #define PI 3.14159 #define MAX_SIZE 1024 #define VERSION "1.0.0"
2. 带参宏
/* 计算两个数的最大值 */ #define MAX(a, b) ((a) > (b) ? (a) : (b)) /* 宏的常见错误:漏加括号 */ #define SQUARE(x) x * x /* 错误:SQUARE(1+1) 变成 1+1*1+1 = 3 */ #define SQUARE(x) ((x) * (x)) /* 正确 */ /* 多语句宏,用 do {...} while(0) 包装 */ #define SET_ARRAY(ptr, val, n) \ do { \ for (int i = 0; i < (n); i++) (ptr)[i] = (val); \ } while(0)
3. 字符串化与连接
/* #x 将参数转为字符串 */ #define PRINT_INT(x) printf(#x " = %d\n", x) PRINT_INT(i + j); /* 输出: i + j = 123 */ /* ## 连接标识符 */ #define GENERATE_FUNC(name) int func_##name() { return 0; } GENERATE_FUNC(test); /* 生成 func_test() */
三、条件编译
/* 跨平台兼容 */ #if defined(__linux__) /* Linux 平台代码 */ #include#elif defined(_WIN32) /* Windows 平台代码 */ #include #else /* 其他平台 */ #endif
/* 调试开关 */ #define DEBUG /* 打开调试模式,删除这行则关闭 */ #ifdef DEBUG printf("Debug: x = %d\n", x); #endif /* 防止重复包含 */ #ifndef HEADER_H #define HEADER_H /* 头文件内容 */ #endif
四、预定义宏
/* C 标准预定义宏 */ __FILE__ /* 当前源文件名字符串 */ __LINE__ /* 当前行号整数 */ __DATE__ /* 编译日期字符串 */ __TIME__ /* 编译时间字符串 */ __STDC__ /* C 标准符合标记,值为 1 表示符合 ISO 标准 */ __STDC_VERSION__ /* C 标准版本,如 201710L 表示 C17 */
/* 调试日志宏示例 */ #define LOG(fmt, ...) \ fprintf(stderr, "[%s:%d] " fmt "\n", \ __FILE__, __LINE__, ##__VA_ARGS__) LOG("Error code: %d", errno);
五、#pragma 指令
/* 关闭特定警告 */ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-variable" int unused_VARiable; /* 不会产生警告 */ #pragma GCC diagnostic pop /* 结构体字节对齐 */ #pragma pack(push, 1) struct Packed { char c; int i; }; /* 5 字节,不用 pack 是 8 字节 */ #pragma pack(pop) /* 条件编译检查 C 标准 */ #if __STDC_VERSION__ < 201112L #error "需要 C11 标准支持" #endif
六、常见陷阱与最佳实践
宏的常见问题:
- 宏不是类型安全的,没有类型检查
- 宏参数要加括号,否则可能出现优先级问题
- 宏如果包含多语句,要用 do-while 包装
- 调试时宏难以单步执行
宏 vs inline 函数
/* C99+ 推荐用 inline 函数替代简单宏 */ static inline int max(int a, int b) { return a > b ? a : b; }
inline 函数有类型检查、更安全、调试友好,建议替代简单的数学宏。但宏在条件编译、类型泛型等方面仍有不可替代的优势。
七、实用宏汇总
/* 获取数组大小 */ #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) /* 防止编译器优化( volatile 强制每次从内存读取) */ #define READ_ONCE(x) (*(volatile typeof(x) *)&(x)) /* .container_of - 从成员指针获取结构体指针 */ #define container_of(ptr, type, member) \ ((type *)((char *)(ptr) - offsetof(type, member)))
八、总结
- 宏是纯文本替换,在编译前完成展开
- 带参宏的参数和结果都要加括号,防止优先级问题
- 用
#ifndef+#define防止头文件重复包含 - 用
#ifdef实现跨平台兼容和调试开关 - 调试困难时考虑用 inline 函数替代
- 使用
gcc -E查看预处理结果