C语言宏用法总结
目录
- 宏定义
1.1 宏常量
1.2 宏语句
1.3 宏函数
1.4 带参数宏定义 - 运算符
2.1 #运算符
2.2 ##运算符 - 可变宏
3.1 基本定义
3.2 空参数处理
3.3 注意事项 - 预定义宏与高级技巧
4.1 预定义宏
4.2 条件编译
4.3 调试技巧
4.4 替代方案
1. 宏定义
1.1 宏常量
- 不带参数
#define PI 3.14
- 带参数
#define SQUARE(x) ((x) * (x))
1.2 宏语句
- 不带参数
#define LOG_HELLO printf("hello")
- 带参数
#define LOG_MSG(x) printf("%s\n", x)
1.3 宏函数
- 格式规范
使用do { ... } while(0)
避免语法问题:#define SAFE_CALL(x) do { func(x); cleanup(); } while(0)
1.4 带参数宏定义
- 核心规则
- 参数替换为符号,无类型检查
- 宏名与括号间不能有空格
- 与函数的区别
特性 | 宏 | 函数 |
---|---|---|
展开时间 | 预处理阶段 | 运行时 |
类型检查 | 无 | 有 |
内存分配 | 无 | 需要栈空间 |
代码体积 | 展开后代码膨胀 | 固定体积 |
2. 运算符
2.1 #
运算符
- 作用:将参数转换为字符串
#define STR(x) #x STR(hello) // 展开为 "hello"
2.2 ##
运算符
- 作用:强制拼接标识符
#define CONCAT(a, b) a##b CONCAT(var, 1) // 展开为 var1
- 必要性
- 直接拼接
xy
会保留空格,##
确保合并为单一标识符
- 直接拼接
3. 可变宏
3.1 基本定义
#define LOG(fmt, ...) printf(fmt, __VA_ARGS__)
LOG("Value: %d", 42); // 展开为 printf("Value: %d", 42)
3.2 空参数处理
标准 | 解决方案 | 示例 |
---|---|---|
GNU | ##__VA_ARGS__ |
printf(fmt, ##__VA_ARGS__) |
C99 | 手动添加空参数占位符 | LOG("Hello", ); |
C11 | __VA_OPT__(,) |
printf(fmt __VA_OPT__(,) ...) |
3.3 注意事项
- 参数顺序:
...
必须位于参数列表末尾 - 编译器兼容性
- GCC:支持
##__VA_ARGS__
- MSVC:需启用
/Zc:preprocessor
- GCC:支持
- 类型安全:需手动匹配格式化字符串与参数类型
4. 预定义宏与高级技巧
4.1 预定义宏
- 编译器内置宏:
用于获取编译环境或代码位置信息:printf("File: %s, Line: %d\n", __FILE__, __LINE__); // 输出当前文件和行号 printf("Compiled on: %s %s\n", __DATE__, __TIME__); // 输出编译日期和时间
4.2 条件编译与宏结合
- 根据宏定义选择代码路径:
#define DEBUG 1 #if DEBUG #define LOG(x) printf("[DEBUG] %s\n", x) #else #define LOG(x) (void)0 #endif
- 检查宏是否存在:
#ifdef PI // PI已定义时执行 #else #define PI 3.14 #endif
4.3 宏调试技巧
- 查看宏展开结果:
使用编译器选项(如GCC的-E
)输出预处理后的代码:gcc -E source.c -o source.i
- 静态断言(C11):
结合宏实现编译时检查:#define STATIC_ASSERT(cond) _Static_assert(cond, #cond) STATIC_ASSERT(sizeof(int) == 4); // 若int不为4字节则编译失败
4.4 宏的替代方案
- 内联函数:
避免宏的副作用,支持类型检查:static inline int square(int x) { return x * x; }
- 泛型选择(C11):
使用_Generic
替代宏实现泛型逻辑:#define TYPE_NAME(x) _Generic((x), \ int: "int", double: "double", default: "unknown")
补充:宏的常见陷阱与最佳实践
-
多次求值问题:
避免在宏参数中使用自增/自减操作:#define SQUARE(x) ((x) * (x)) int a = 5; SQUARE(a++); // 展开为 (a++) * (a++) → 结果不可预测!
-
参数未括号化:
宏参数需用括号包裹以防止运算符优先级问题:#define ADD(x, y) x + y // 错误:ADD(1, 2)*3 → 1 + 2*3 #define SAFE_ADD(x, y) ((x) + (y)) // 正确
-
命名冲突与作用域管理:
- 使用
#undef
释放不再需要的宏:#define TEMP_MACRO 42 // 使用后释放 #undef TEMP_MACRO
- 在局部作用域内定义宏(通过
#pragma push_macro/pop_macro
,部分编译器支持)。
- 使用
```