STM32 学习笔记:理解 my_printf
与 va_start
在嵌入式开发中,我们常常需要实现类似标准 C 中 printf
的调试输出功能。为了支持“任意数量参数”的传递,C 语言提供了对 可变参数(variable arguments) 的支持。其中,va_start
是这一机制中的核心宏之一。
以下我们将结合一个实际的自定义打印函数 my_printf
,详细讲解其工作原理,并深入解析 va_start
在其中的作用。
一、函数介绍:my_printf
1. 函数声明
int my_printf(UART_HandleTypeDef *huart, const char *format, ...)
-
参数说明:
UART_HandleTypeDef *huart
:指向 STM32 HAL 库中 UART 句柄结构体,用于指定使用的串口。const char *format
:格式化字符串,如"Voltage: %.2f V"
。...
:可变参数列表,表示可以传入任意多个附加参数。
-
返回值:
- 返回格式化后字符串的长度(写入缓冲区的字符数)。
2. 局部变量声明
char buffer[512]; // 缓冲区,用于存储格式化后的字符串
va_list arg; // 可变参数列表
int len; // 字符串长度
buffer
是临时存储空间,防止直接操作堆栈;arg
是stdarg.h
提供的类型,用于访问可变参数;len
用于记录最终写入缓冲区的字符数。
3. 初始化可变参数列表
va_start(arg, format);
- 使用
va_start
宏初始化arg
,使其指向第一个可变参数; format
是最后一个固定参数,用于定位后续参数的位置。
4. 格式化字符串
len = vsnprintf(buffer, sizeof(buffer), format, arg);
- 使用
vsnprintf
将格式化字符串写入buffer
; - 支持
%d
,%s
,%f
等占位符; - 防止缓冲区溢出,安全性高于
sprintf
。
5. 清理可变参数列表
va_end(arg);
- 调用
va_end
结束对可变参数的访问; - 必须成对出现,否则可能导致未定义行为。
6. 通过 UART 发送数据
HAL_UART_Transmit(huart, (uint8_t *)buffer, (uint16_t)len, 0xFF);
- 将格式化好的字符串通过串口发送到上位机(如串口助手);
- 实现了“调试信息输出”的基础功能。
7. 返回值
return len;
- 返回实际写入的字符数,可用于日志统计或错误处理。
二、关键宏 va_start
深度解析
1. va_start
的作用
va_start
是 <stdarg.h>
头文件提供的宏,用于初始化可变参数列表。它告诉编译器从哪个位置开始读取可变参数。
void va_start(va_list ap, last_fixed_param);
ap
:va_list
类型变量,用于遍历参数;last_fixed_param
:最后一个固定参数(即可变参数前的一个参数),用于定位起始位置。
2. 示例代码:手动实现一个可变参数函数
#include <stdio.h>
#include <stdarg.h>
int my_sum(int count, ...) {
va_list args;
va_start(args, count); // 初始化 args,从 count 后的第一个参数开始
int sum = 0;
for (int i = 0; i < count; i++) {
int num = va_arg(args, int); // 获取每个 int 参数
sum += num;
}
va_end(args); // 清理资源
return sum;
}
int main() {
printf("Sum: %d\n", my_sum(4, 10, 20, 30, 40)); // 输出:Sum: 100
return 0;
}
✅ 本例展示了如何通过
va_start
+va_arg
实现一个简单的求和函数。
三、va_start
在 my_printf
中的作用
1. 为什么需要 va_start
?
因为 my_printf
是一个可变参数函数(类似 printf
),我们需要一种方式去获取 format
之后的所有参数。va_start
就是这个过程的关键步骤。
2. va_start
在代码中的流程:
va_start(arg, format); // 初始化 args,指向第一个可变参数
len = vsnprintf(buffer, sizeof(buffer), format, arg); // 把参数格式化进 buffer
va_end(arg); // 清理资源
arg
会被vsnprintf
使用,自动解析所有参数;- 如果不调用
va_start
,vsnprintf
将无法正确识别参数。
四、完整流程总结
步骤 | 描述 |
---|---|
1. 接收参数 | 包括 UART 句柄、格式化字符串和可变参数 |
2. 初始化缓冲区和可变参数 | 声明 buffer 和 arg ,并调用 va_start |
3. 格式化字符串 | 使用 vsnprintf 安全地格式化为字符串 |
4. 清理可变参数 | 调用 va_end 释放资源 |
5. 通过 UART 发送数据 | 使用 HAL_UART_Transmit 发送到串口 |
6. 返回结果 | 返回格式化后的字符数 |