va_list 原理以及用法

VA_LIST 是在C语言中解决变参问题的一组宏,变参问题是指参数的个数不定,可以是传入一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有实际的名称与之相对应,用起来是很灵活。

va_list 用法示例

#include <stdarg.h> 

int AveInt(int,...);

 void main()
{
   printf("%d/t",AveInt(2,2,3));
   printf("%d/t",AveInt(4,2,4,6,8));
   
   return;
}

int AveInt(int v,...)
{
   int ReturnValue=0;
   int i=v;
   
   va_list ap ;
   va_start(ap,v);

   while(i>0)
   {
	   ReturnValue+=va_arg(ap,int) ;
       i--;
   }
   va_end(ap); 
   return ReturnValue/=v;
}

VA_LIST的用法:
(1)首先在函数里定义一具VA_LIST型的变量,这个变量是指向参数的指针;
(2)然后用VA_START宏初始化变量刚定义的VA_LIST变量;
(3)然后用VA_ARG返回可变的参数,VA_ARG的第二个参数是你要返回的参数的类型(如果函数有多个可变参数的,依次调用VA_ARG获取各个参数);
(4)最后用VA_END宏结束可变参数的获取。

上面是va_list的具体用法,下面讲解一下va_list各个语句含义(如上示例黑体部分)和va_list的实现。

     可变参数是由宏实现的,但是由于硬件平台的不同,编译器的不同,宏的定义也不相同,下面是VC6.0中x86平台的定义 :

      typedef char * va_list;     // TC中定义为void*
      #define _INTSIZEOF(n)    ((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1) ) //为了满足需要内存对齐的系统
      #define va_start(ap,v)    ( ap = (va_list)&v + _INTSIZEOF(v) )     //ap指向第一个变参的位置,即将第一个变参的地址赋予ap
      #define va_arg(ap,t)       ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )   /*获取变参的具体内容,t为变参的类型,如有多个参数,则通过移动ap的指针来获得变参的地址,从而获得内容*/
      #define va_end(ap) ( ap = (va_list)0 )   //清空va_list,即结束变参的获取

va_list ap ; 定义一个va_list变量ap
va_start(ap,v) ;执行ap = (va_list)&v + _INTSIZEOF(v),ap指向参数v之后的那个参数的地址,即 ap指向第一个可变参数在堆栈的地址。
va_arg(ap,t) , ( (t )((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )取出当前ap指针所指的值,并使ap指向下一个参数。 ap+= sizeof(t类型),让ap指向下一个参数的地址。然后返回ap-sizeof(t类型)的t类型指针,这正是第一个可变参数在堆栈里的地址。然后 用取得这个地址的内容。
va_end(ap) ; 清空va_list ap。

使用VA_LIST应该注意的问题:
(1)因为va_start, va_arg, va_end等定义成宏,所以它显得很愚蠢,可变参数的类型和个数完全在该函数中由程序代码控制,它并不能智能地识别不同参数的个数和类型. 也就是说,你想实现智能识别可变参数的话是要通过在自己的程序里作判断来实现的.
(2)另外有一个问题,因为编译器对可变参数的函数的原型检查不够严格,对编程查错不利.不利于我们写出高质量的代码。
(3)由于参数的地址用于VA_START宏,所以参数不能声明为寄存器变量,或作为函数或数组类型。

参考文章:
http://blog.youkuaiyun.com/aihao1984/article/details/5953668

<think>好的,我现在需要帮用户解决关于STM32 HAL库自定义u1_printf函数的问题,涉及到使用vsprintf和UART发送字符,以及__HAL_UART_GET_FLAG宏的应用。首先,我得回忆一下STM32 HAL库中UART相关的函数和机制。 用户提到了引用中的重定向fputc和fgetc的方法,以及使用Seria2_Printf函数。这说明用户可能想了解如何不依赖标准库函数,而是自己实现printf功能,通过UART发送数据。需要解释如何组合vsprintf和HAL_UART_Transmit,或者直接操作寄存器发送字符。 首先,自定义printf函数通常需要处理可变参数,这可以用va_listva_start、va_end等宏来实现。vsprintf可以将格式化字符串和参数转换成字符串,然后通过UART发送出去。这部分在引用[2]中已经有示例,用户可能想更详细地了解这个过程,或者如何结合状态标志来发送数据。 然后,用户提到__HAL_UART_GET_FLAG,这可能涉及到检查UART的状态标志,比如发送寄存器是否为空(TXE)或者传输是否完成(TC)。在HAL库中,通常使用宏如__HAL_UART_GET_FLAG(&huart1, UART_FLAG_TXE)来检查这些标志,以确保数据可以安全发送。 需要分步骤解释如何实现自定义的u1_printf函数。第一步是处理可变参数,使用va_list来获取参数列表,然后使用vsprintf将格式化的字符串存入缓冲区。第二步是发送这个缓冲区的内容,可以通过HAL_UART_Transmit函数直接发送,或者逐个字符发送并检查标志位,比如使用轮询方式等待发送完成。 另外,用户可能想知道如何避免使用HAL库的阻塞函数(如HAL_MAX_DELAY),转而使用非阻塞方式或者更高效的方法。例如,在引用[1]中的示例有两种方法:一种使用HAL_UART_Transmit,另一种直接操作寄存器,并轮询状态标志。需要解释这两种方法的区别,以及如何选择适合的方式。 还可能需要注意缓冲区溢出的问题,因为vsprintf如果格式化后的字符串超过缓冲区大小会导致问题。建议用户使用更安全的函数如vsnprintf来限制写入的字符数量。 最后,需要回答用户关于__HAL_UART_GET_FLAG的使用,说明在自定义发送函数中如何利用这个宏来检查发送状态,确保每个字符都正确发送出去。比如,在发送每个字符前检查UART_FLAG_TXE是否置位,表示数据寄存器为空,可以接收新数据。 总结步骤:处理可变参数、格式化字符串、UART发送数据(使用HAL函数或直接操作寄存器)、状态标志检查,以及安全措施。同时需要对比不同方法的优缺点,比如阻塞与非阻塞,资源占用情况等。</think>### STM32 HAL库自定义`u1_printf`函数实现详解 #### 1. **实现原理** 自定义`printf`函数的核心是**处理可变参数**和**格式化字符串**,再通过UART发送数据。实现需以下步骤: 1. **参数处理**:使用`va_list`、`va_start`、`va_end`宏处理可变参数。 2. **字符串格式化**:利用`vsprintf`或`vsnprintf`将参数格式化为字符串。 3. **UART发送**:通过HAL库的`HAL_UART_Transmit`或直接操作寄存器发送数据。 #### 2. **代码实现** ##### 方法一:基于HAL库的阻塞发送 ```c #include <stdarg.h> void u1_printf(char *format, ...) { char buffer[128]; // 定义缓冲区 va_list args; va_start(args, format); vsnprintf(buffer, sizeof(buffer), format, args); // 安全格式化 va_end(args); HAL_UART_Transmit(&huart1, (uint8_t*)buffer, strlen(buffer), HAL_MAX_DELAY); // 阻塞发送 } ``` - **优点**:代码简洁,依赖HAL库的可靠性。 - **缺点**:`HAL_MAX_DELAY`可能阻塞程序运行[^1]。 ##### 方法二:手动检查发送状态(非阻塞) ```c void u1_printf(char *format, ...) { char buffer[128]; va_list args; va_start(args, format); vsnprintf(buffer, sizeof(buffer), format, args); va_end(args); uint8_t *p = (uint8_t*)buffer; while (*p) { // 等待发送寄存器为空 while (!__HAL_UART_GET_FLAG(&huart1, UART_FLAG_TXE)) {} huart1.Instance->DR = *p++; // 直接写入数据寄存器 } } ``` - **关键宏**:`__HAL_UART_GET_FLAG(&huart1, UART_FLAG_TXE)`检查发送缓冲区是否就绪[^1]。 - **优点**:避免阻塞,适合实时性要求高的场景。 - **注意**:需确保UART已初始化,且未启用DMA/中断。 #### 3. **关键机制解析** - **`vsprintf`的作用**:将格式化字符串和参数合并为完整字符串,例如`vsprintf(buffer, "Value=%d", 123)`生成`"Value=123"`。 - **状态标志检查**: - `UART_FLAG_TXE`:发送寄存器为空,可写入新数据。 - `UART_FLAG_TC`:发送完成(包括移位寄存器)。 - **直接操作寄存器**:通过`huart1.Instance->DR`写入数据,需配合状态检查确保时序正确。 #### 4. **安全性优化** - **缓冲区限制**:使用`vsnprintf`替代`vsprintf`,避免溢出。 - **超时机制**:在非阻塞发送中可添加超时计数器,防止死循环。 ```c while (!__HAL_UART_GET_FLAG(&huart1, UART_FLAG_TXE) && timeout++ < 1000); if (timeout >= 1000) { // 错误处理 } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值