21 可变参数宏与日志封装
一、核心结论
... 接收可变数量的宏实参,__VA_ARGS__ 将这些实参放进宏替换结果。 宏只负责预处理展开,真正输出日志的是 printf 或 SDK 日志函数。
#include <stdio.h>
#define LOG(...) printf(__VA_ARGS__)
int main(void)
{
LOG("BLE connected\n");
LOG("RSSI=%d\n", -60);
return 0;
}
输出:
BLE connected
RSSI=-60
二、宏展开流程图
LOG("RSSI=%d\n", -60)
│
▼ 预处理替换 __VA_ARGS__
printf("RSSI=%d\n", -60)
│
▼ 运行时 printf 格式化
RSSI=-60
...:定义可变参数部分。__VA_ARGS__:引用对应的实参序列。- 不会在运行时拼接字符串,也不会由宏本身执行打印。
三、二极管对照:两种日志宏
#define LOG1(...) printf(__VA_ARGS__)
#define LOG2(fmt, ...) printf(fmt, __VA_ARGS__)
| 情况(严格 C11) | LOG1(...) | LOG2(fmt, ...) |
|---|---|---|
| 只有格式字符串 | 支持:LOG1("OK\n") | 不能省略可变参数部分 |
| 格式串加数值 | 支持:LOG1("%d", 3) | 支持:LOG2("%d", 3) |
| 简单转发日志 | 推荐 | 容易遇到尾随逗号问题 |
为什么 LOG2("OK\n") 不行?
#define LOG2(fmt, ...) printf(fmt, __VA_ARGS__)
/* 严格 C11:未提供所需的可变参数部分 */
/* LOG2("OK\n"); */
在严格 C11 中,对含有固定参数 fmt 的可变参数宏,调用时需要提供可变参数部分。即使某些扩展接受省略,简单替换也会留下无效代码:
printf("OK\n", ); // 多余的逗号,语法错误
本篇限定严格 C11,不讨论 GNU
, ##__VA_ARGS__或 C23__VA_OPT__。
四、TWS 固件中的简易日志入口
#include <stdio.h>
#define BLE_LOG(...) printf(__VA_ARGS__)
void on_connected(int handle)
{
BLE_LOG("BLE connected\n");
BLE_LOG("handle=%d\n", handle);
}
在实际固件中,printf 可由 SDK 的日志输出接口替代;ISR、实时音频任务等执行上下文是否允许同步打印,应遵守 SDK 要求。本篇不展开日志等级、延迟打印等其他话题。
五、速记
...收参数,__VA_ARGS__转参数。LOG(...)能转发只有格式字符串的调用,也能转发格式串与额外参数。- 简单的
LOG(fmt, ...)在严格 C11 下不能直接省略可变参数部分;否则还可能留下printf(fmt, )。 - 宏在预处理时展开,日志函数在运行时输出。