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);

  }

}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章