C语言宏用法总结

目录

  1. 宏定义
    1.1 宏常量
    1.2 宏语句
    1.3 宏函数
    1.4 带参数宏定义
  2. 运算符
    2.1 #运算符
    2.2 ##运算符
  3. 可变宏
    3.1 基本定义
    3.2 空参数处理
    3.3 注意事项
  4. 预定义宏与高级技巧
    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 注意事项

  1. 参数顺序... 必须位于参数列表末尾
  2. 编译器兼容性
    • GCC:支持 ##__VA_ARGS__
    • MSVC:需启用 /Zc:preprocessor
  3. 类型安全:需手动匹配格式化字符串与参数类型

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")
    

补充:宏的常见陷阱与最佳实践

  1. 多次求值问题
    避免在宏参数中使用自增/自减操作:

    #define SQUARE(x) ((x) * (x))
    int a = 5;
    SQUARE(a++); // 展开为 (a++) * (a++) → 结果不可预测!
    
  2. 参数未括号化
    宏参数需用括号包裹以防止运算符优先级问题:

    #define ADD(x, y) x + y       // 错误:ADD(1, 2)*3 → 1 + 2*3
    #define SAFE_ADD(x, y) ((x) + (y)) // 正确
    
  3. 命名冲突与作用域管理

    • 使用#undef释放不再需要的宏:
      #define TEMP_MACRO 42
      // 使用后释放
      #undef TEMP_MACRO
      
    • 在局部作用域内定义宏(通过#pragma push_macro/pop_macro,部分编译器支持)。

```