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開頭的宏
typedef char * va_list;
va_star
#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
#define va_end _crt_va_end
#define _crt_va_end(ap) ( ap = (va_list)0 )
va_arg
#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);
}
}