优秀的编程知识分享平台

网站首页 > 技术文章 正文

【C语法硬核20讲】07 宏与预处理:写出安全宏

nanyue 2025-09-14 23:57:02 技术文章 2 ℃

目标:掌握 预处理(preprocessing) 的关键能力:字符串化(#)、记号拼接(##)、可变参数宏、do{...}while(0) 惯用法、全括号、无副作用、以及 C11 _Generic 做“类型感知宏”。给你一套可直接抄用的宏模板。


1)常量优先用 enum/static const

enum { SZ_PAGE = 4096 };            // 编译期常量,参与 case/数组维度
static const int MAX_CONN = 1024;   // 只读变量,比 #define 更安全

2)表达式宏三件套:全括号 + 无副作用 + do-while(0)

#define SQR(x) ((x) * (x))             // 参数+整体都加括号
// 语句式宏:保证使用时像一条语句
#define PUSH(v) do { stack[top++] = (v); } while (0)

禁忌:在宏参数中产生副作用(如 x++):

#define BAD_MIN(a,b) ((a)<(b)?(a):(b))
int m = BAD_MIN(i++, j++); //  副作用求值两次

替代:用 static inline 函数或 C11 _Generic

#define MIN(a,b) _Generic((a)+(b), \
    long double: fminl, double: fmin, float: fminf, default: NULL)(a,b)

没有合适重载时,直接用 static inline 是首选。


3)字符串化与记号拼接

#define STR(x) #x
#define CAT(a,b) a##b

printf("%s\n", STR(Hello World));  // → "Hello World"
int CAT(user, _id) = 7;            // → int user_id = 7;

4)可变参数宏(日志/包装)

#define LOG_INFO(fmt, ...) \
    do { fprintf(stderr, "[I] %s:%d " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__); } while(0)

说明:##__VA_ARGS__ 是 GNU 扩展,可消除“空参数”时的多余逗号;要可移植,把逗号放进 fmt 控制。


5)类型安全的“数组元素个数”宏

// 仅在实数组上成立,传指针会编译期报错(C11)
#define COUNT_OF(a) \
    (sizeof(a)/sizeof((a)[0]) + 0* _Generic(&(a), const void *: 0, default: 0))

简洁可用的版本:#define COUNT_OF(a) (sizeof(a)/sizeof((a)[0]))(注意:只能用于定义处数组)。


6)宏的作用域与防污染

  • 命名建议:全大写 + 前缀(如 UTIL_COUNT_OF)。
  • 用完 #undef 清场;头文件使用 include guard 或 #pragma once。
  • 只在私有 .c 使用的内部宏不进公共头。

7)宏与内联的取舍

  • 可以纯用宏的,多数也能改成 static inline:更安全、可调试、有类型检查。
  • 一定要用宏的场景:#if 条件编译、文件名/行号注入、生成标识符等。

8)完整模板库(收藏即用)

// 语句式宏
#define DEFER(stmt) do { stmt; } while(0)

// 字符串化/拼接
#define STR(x) #x
#define CAT(a,b) a##b

// 断言(简版)
#define ASSERT(cond) do { \
    if(!(cond)){ fprintf(stderr,"ASSERT %s:%d: %s\n",__FILE__,__LINE__,#cond); abort(); } \
} while(0)

// scope guard(需要 GNU 扩展时更强)
static inline void swap_int(int* a,int* b){ int t=*a;*a=*b;*b=t; }

#define COUNT_OF(a) (sizeof(a)/sizeof((a)[0])) // 仅数组定义处

9)自检清单

  • 参数与整体全括号
  • 语句式宏用 do{...}while(0)
  • 避免副作用;必要时改 static inline
  • #/## 熟练使用
  • 日志宏带 __FILE__/__LINE__
  • include guard/#pragma once 防重复包含

10)小练习

1)把 SWAP(a,b) 写成安全宏或 static inline。
2)写一个 LOG_ERR,当 errno 非 0 时附带错误字符串。

最近发表
标签列表