什麼是變長參數?
所謂含有變長參數的函數是指該函數可以接受可變數目的形參。例如我們都非常熟悉的
printf,scanf等等。
2:變長參數如何??
首先來看下面這樣一個例子:
#include<stdio.h>
#include<stdarg.h>
#include<string.h>
void demo(char *msg,...)
{
va_list argp;
int arg_number=0;
char *para = msg;
va_start(argp,msg);
while(1)
{
if ( strcmp( para, "/0") != 0 )
{
arg_number++;
printf("parameter %d is: %s/n",arg_number,para);
}
else
break;
para = va_arg(argp,char *);
}
va_end(argp);
}
int main()
{
demo("Hello","World","/0");
system("pause");
return 0;
}
實現這樣一個函數要在內部使用va_list,va_start,va_arg,va_end,這些都是定義在
stdarg.h中的宏。
va_list是定義了一個儲存函數參數的資料結構。
va_start(argp,msg)是將argp指向第一個可變參數,而msg是最後一個確定的參數。
最後一個確定的參數的含義是指它以後的參數都是可變參數,如果有下面的函式宣告
void demo(char *msg1,char *msg2,...)
那麼這裡的最後一個確定參數就是msg2。
va_arg(argp,char *)返回當前參數的值,類型為char *,然後將argp指向下一個變長參
數。從這一步可以看出來我們可以通過va_start和va_arg遍曆所有的變長參數。
va_end 將argp的值置為0。
下面我們看看上述幾個宏在visual c++.net 2003 中的實現方法。首先是va_list的實現
#ifdef _M_ALPHA
typedef struct {
char *a0; /* pointer to first homed integer argument */
int offset; /* byte offset of next parameter */
} va_list;
#else
typedef char * va_list;
#endif
可以看到va_list實際上是一個機器類型相關的宏,除了alpha機器以外,其他機器類
型都被定義為一個char類型的指標變數,之所以定義為char *是因為可以用該變數逐
地址也就是逐位元組對參數進行遍曆。
從上面可以看到,這些宏的實現都是和機器相關的,下面是大家常用的IX86機器下宏的
相關定義。
#elif defined(_M_IX86)
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
#define va_start(ap,v) ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define va_end(ap) ( ap = (va_list)0 )
#ifdef __cplusplus
#define _ADDRESSOF(v) ( &reinterpret_cast<const char &>(v) )
#else
#define _ADDRESSOF(v) ( &(v) )
#endif
首先看_INTSIZEOF(n)
我們知道對於IX86,sizeof(int)一定是4的整數倍,所以~(sizeof(int) - 1) )的值一定是
右面[sizeof(n)-1]/2位為0,整個這個宏也就是保證了右面[sizeof(n)-1]/2位為0,其餘位置
為1,所以_INTSIZEOF(n)的值只有可能是2,4,8,16,......等等,實際上是實現了位元組對齊。
#define va_start(ap,v) ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )
所以va_start(ap,v)的作用就很明了了,_ADDRESSOF(v)定義了v的起始地址,_INTSIZEOF(v)定義了v所
佔用的記憶體,所以ap 就指向v後面的參數的起始地址。
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
ap += _INTSIZEOF(t) 使ap指向了後面一個參數的地址
而( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )相當於返回了目前t類型的參數的值。
#define va_end(ap) ( ap = (va_list)0 )
將變數ap 的值置為0。
通過上述分析,再次印證了我麼前面對可變參數實現的解釋。
因此我們可以總結出變長參數函數的一般實現方法:
1:聲明原型,形如void demo(char *msg,...),注意變長參數的原型聲明中至少要含有
一個確定參數。
2:用va_list定義儲存函數參數的資料結構,可以理解為一個指標變數(稍後會解釋)。
3:用va_start將上一步定義的變數指向第一個可變參數。
4:用va_arg遍曆所有的可變參數。
5:用va_end將指標變數持有的地址值置為0。