va_list 、vsnprintf 原理及用法

本文详细介绍了C语言中的VA_LIST宏及其在处理变长参数列表的应用,并提供了具体的示例代码。同时,还深入探讨了_vsnprintf函数的使用方法及注意事项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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


下面是 <stdarg.h> 里面重要的几个宏定义如下:
typedef char* va_list;
void va_start ( va_list ap, prev_param ); /* ANSI version */
type va_arg ( va_list ap, type );
void va_end ( va_list ap );
va_list 是一个字符指针,可以理解为指向当前参数的一个指针,取参必须通过这个指针进行。
<Step 1> 在调用参数表之前,定义一个 va_list 类型的变量,(假设va_list 类型变量被定义为ap);
<Step 2> 然后应该对ap 进行初始化,让它指向可变参数表里面的第一个参数,这是通过 va_start 来实现的,第一个参数是 ap 本身,第二个参数是在变参表前面紧挨着的一个变量,即“...”之前的那个参数;
<Step 3> 然后是获取参数,调用va_arg,它的第一个参数是ap,第二个参数是要获取的参数的指定类型,然后返回这个指定类型的值,并且把 ap 的位置指向变参表的下一个变量位置;
<Step 4> 获取所有的参数之后,我们有必要将这个 ap 指针关掉,以免发生危险,方法是调用 va_end,他是输入的参数 ap 置为 NULL,应该养成获取完参数表之后关闭指针的习惯。说白了,就是让我们的程序具有健壮性。通常va_start和va_end是成对出现。


下面是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_宏初始化变量刚定义的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,即结束变参的获取


      C语言的函数形参是从右向左压入堆栈的,以保证栈顶是第一个参数,而且x86平台内存分配顺序是从高地址到低地址。因此似函数AVEInt(int var1,int var2,...,int varN)内存分配大致上是这样的:(可变参数在中间)
栈区:
|栈顶             低地址
|第一个参数var1                  <-- &v
|第二个参数var2                  <-- va_start(ap,v)后ap指向地址      
|...
|函数的最后varN
|...
|函数的返回地址
|...
|栈底    高地址

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://hi.baidu.com/kang_liang/blog/item/168c9059a9a1ca2d2934f05f.html
http://fenge.bokee.com/195016.html

http://blog.youkuaiyun.com/homer1984/archive/2009/02/02/3859036.aspx



_vsnprintf,C语言库函数之一,属于可变参数。用于向字符串中打印数据、数据格式用户自定义。

函数简介

编辑
头文件:
#include <stdarg.h>
函数声明:
int _vsnprintf(char* str, size_t size, const char* format, va_list ap);
参数说明:
  1. char *str [out],把生成的格式化的字符串存放在这里.
  2. size_t size [in], str可接受的最大字符数 [1]  (非字节数,一个字符两个字节),防止产生 数组越界.
  3. const char *format [in], 指定输出格式的字符串,它决定了你需要提供的可变参数的类型、个数和顺序。
  4. va_list ap [in], va_list变量. va:variable-argument:可变参数
函数功能:将可变参数格式化输出到一个字符数组。
用法类似于vsprintf,不过加了size的限制,防止了内存溢出(size为str所指的存储空间的大小)。
返回值:执行成功,返回写入到字符数组str中的字符个数(不包含终止符),最大不超过size;执行失败,返回负值,并置 errno. [2]  
备注:
linux环境下是:vsnprintf
VC6环境下是:_vsnprintf

用法实例

编辑
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
#include <stdarg.h>
int  mon_log( char * format, ...)
{
char  str_tmp[50];
int  i=0;
va_list  vArgList;                             //定义一个va_list型的变量,这个变量是指向参数的指针.
va_start  (vArgList, format);                  //用va_start宏初始化变量,这个宏的第二个参数是第一个可变参数的前一个参数,是一个固定的参数
i=_vsnprintf(str_tmp, 50, format, vArgList);  //注意,不要漏掉前面的_
va_end (vArgList);                             //用va_end宏结束可变参数的获取
return  i;                                     //返回参数的字符个数中间有逗号间隔
}
//调用上面的函数
void  main() 
{
     int  i=mon_log( "%s,%d,%d,%d" , "asd" ,2,3,4);
     printf ( "%d\n" ,i);
}
输出 9。
asd,2,3,4
123456789 (共9个字符,间隔符逗号计算在内) [2]  

返回值用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
char  *make_message( const  char  *fmt, ...) 
{
     /* 初始时假设我们只需要不超过100字节大小的空间 */
     int  n, size = 100;
     char  *p;
     va_list  ap;
     if  ( (p = ( char  *)  malloc (size* sizeof ( char ))) == NULL)
     return  NULL;
     while  (1) 
     {
         /* 尝试在申请的空间中进行打印操作 */
         va_start (ap, fmt);
         n = vsnprintf (p, size, fmt, ap);
         va_end (ap);
         /* 如果vsnprintf调用成功,返回该字符串 */
         if  (n > -1 && n < size)
         return  p;
         /* vsnprintf调用失败(n<0),或者p的空间不足够容纳size大小的字符串(n>=size),尝试申请更大的空间*/
         size *= 2;  /* 两倍原来大小的空间 */
         if  ((p = ( char  *) realloc (p, size* sizeof ( char ))) == NULL)
         return  NULL;
     }
}
int  main() 
{
     /* 调用上面的函数 */
     char * str = make_message( "%d,%d,%d,%d" ,5,6,7,8);
     printf ( "%s\n" ,str);
     free (str);
     /* we allocate the memory in the make_message function, so we should release it by caller(main function). */
     return  0;
}
代码在vc6.0下调试通过。


char * aLogInfo[9] = {"EME", "ALT", "CRT", "ERR", "WAR", "NTE", "INF", "DBG"};  /*输入日志等级*/
    char szBuffer[1024];    /* vsprintf的第一个参数 */
    pid_t pid;                      /* 进程的id             */

    pid = getpid();
    va_list ap;
    va_start(ap, pszFormat);
    vsnprintf(szBuffer,1024, pszFormat, ap);
    va_end(ap);
    printf("[%s] PID[%5d] %s\n",
                            aLogInfo[IPriovity], pid, szBuffer);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值