Printf系列函数及va_系列宏(stdarg.h)

1.Printf系列函数  (摘自陈珍敬的CSDN博客)

Printf 函数是一组函数的总称,包含:

       #include <stdio.h>

       int printf(const char *format, ...);

       int fprintf(FILE *stream, const char *format, ...);

       int sprintf(char *str, const char *format, ...);

       int snprintf(char *str, size_t size, const char *format, ...);

 

       #include <stdarg.h>

       int vprintf(const char *format, va_list ap);

       int vfprintf(FILE *stream, const char *format, va_list ap);

       int vsprintf(char *str, const char *format, va_list ap);

       int vsnprintf(char *str, size_t size, const char *format, va_list ap);

       注:这组函数使用可变函数列表作为参数。还包括间接使用 printf 的函数,如 wxWidget 中的 wxLogXXX 等。

 

Printf 函数的 format 参数 使用 % 作为特殊字符用以定义所要打印的串的特殊格式;

 

下面一种格式的printf(#include <stdarg.h> )函数需要用到以va开头的宏,在这边介绍一下:

 

2.va开头的宏

va_list
Code:

       typedef char *  va_list;  

 

va_star

Code:

       #define va_start _crt_va_start   

       #define _crt_va_start(ap,v)  ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )   

  

       #define _ADDRESSOF(v)   ( &reinterpret_cast<const char &>(v) )   

       #define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )//上对齐  

 

va_end

Code:

        #define va_end _crt_va_end   

        #define _crt_va_end(ap)      ( ap = (va_list)0 )  

va_arg

Code:

       #define va_arg _crt_va_arg   

       #define _crt_va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )  

 

函数参数的传递原理:函数参数是以数据结构 : 栈的形式存取 (C 语言在函数调用时,先将最后一个参数压入栈 ) 。在 stdcall 下,堆栈中 , 各个参数的分布情况是倒序的 . 即最后一个参数在列表中地址最高部分 , 第一个参数在列表地址的最低部分 . 参数在堆栈中的分布情况如下 :

最后一个参数

倒数第二个参数

...

第一个参数

函数返回地址

函数代码段

获取省略号指定的参数:在函数体中声明一个 va_list ,然后用 va_start 函数来获取参数列表中的参数,使用完毕后调用 va_end() 结束。举个例子 (VS) :

void TestFun(char* pszDest, int DestLen, const char* pszFormat, ...)

{

va_list args;

va_start(args, pszFormat); // pszFormat 参数下一个参数的地址 , 即第一个可变参数地址 .

_vsnprintf(pszDest, DestLen, pszFormat, args);

va_end(args);

}

原理: va_start 使 argp 指向第一个可选参数。 va_arg 返回参数列表中的当前参数并使 argp 指向参数列表中的下一个参数。 va_end 把 argp 指针清为 NULL 。函数体内可以多次遍历这些参数,但是都必须以 va_start 开始,并以 va_end 结尾。

va_start, va_arg 和 va_end 仅仅是一些宏 , 用于获取参数堆栈地。

 

摘自: wchar.h

#ifdef _M_CEE_PURE

typedef System::ArgIterator va_list;

#else

typedef char *  va_list;

#endif

注 :char 型指针的特点是 ++ 、 -- 操作对其作用的结果是增 1 和减 1 (因为 sizeof(char) 为 1 )

 

摘自 stdarg.h

#define va_start _crt_va_start

#define va_arg _crt_va_arg

#define va_end _crt_va_end

 

摘自 *vadefs.h - defines helper macros for stdarg.h

/* A guess at the proper definitions for other platforms */( 不同平台定义不同,指针是依赖平台的 )

#ifdef  __cplusplus

#define _ADDRESSOF(v)   ( &reinterpret_cast<const char &>(v) )

#else

#define _ADDRESSOF(v)   ( &(v) )

#endif

 

#define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) //4 的倍数

 

#define _crt_va_start(ap,v)  ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )

#define _crt_va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )

#define _crt_va_end(ap)      ( ap = (va_list)0 )

 

注: _INTSIZEOF(n) 宏是为了考虑那些内存地址需要对齐的系统,从宏的名字来应该是跟 sizeof(int) 对齐。一般的 sizeof(int)= 4 ,也就是参数在内存中的地址都为 4 的倍数。比如,如果 sizeof(n) 在 1 - 4 之间,那么 _INTSIZEOF(n) = 4 ;如果 sizeof(n) 在 5 - 8 之间,那么 _INTSIZEOF(n)=8 。具体细节可以参见:数据在机器内的存储与运算章节。

va_arg(ap,t) 得到第一个参数的值 ( 即调用前 ap 的值 ), 并且将 ap 指针上移一个 _INTSIZEOF(t), 即指向下一个可变参数的地址。这个宏分两步: 1) (ap += _INTSIZEOF(t)) 移动指针地址 ; 2) 使用移动后的 ap 值计算原先的参数值 ( 减掉移动值 _INTSIZEOF(t)) 。

 

如何直接只用可变参数列表:

虽然可以通过在堆栈中遍历参数列表来读出所有的可变参数 , 但是想直接使用可变参数列表,还需要知道可变参数的个数 , 以便结束遍历。解决办法 : a. 参数指定 : 在第一个起始参数中指定参数个数 , 那么就可以在循环还中读取所有的可变参数 ; b. 位哨 : 定义一个结束标记 , 在调用函数的时候 , 在最后一个参数中传递这个标记 , 这样在遍历可变参数的时候 , 可以根据这个标记结束可变参数的遍历。举例:

void arg_cnt(int cnt, ...)

{

  int value=0;

  int i=0;

  int arg_cnt=cnt;

  va_list arg_ptr;

  va_start(arg_ptr, cnt);

  for(i = 0; i < cnt; i++)

  {

  value = va_arg(arg_ptr,int);

  printf("value%d=%d/n", i+1, value);

  }

}

你可能感兴趣的:(Printf系列函数及va_系列宏(stdarg.h))