va_start,va_arg,va_end是在stdarg.h中被定義成宏的,
因爲1)硬件平臺的不一樣 2)編譯器的不一樣,因此定義的宏也有所不一樣,下
面以VC++中stdarg.h裏x86平臺的宏定義摘錄以下(’\’號表示折行):
typedef char * va_list;
#define _INTSIZEOF(n) \
((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1) )
#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
#define va_arg(ap,t) \
( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define va_end(ap) ( ap = (va_list)0 )
另一種實現:linux
typedef char * va_list;//va_list 等價於char*即字符指針。
#define va_start _crt_va_start//注意下面的替代。
#define va_arg _crt_va_arg
#define va_end _crt_va_end
#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 )
va_list argptr;編程
定義_INTSIZEOF(n)主要是爲了某些須要內存的對齊的系統.C語言的函
數是從右向左壓入堆棧的,圖(1)是函數的參數在堆棧中的分佈位置.我
們看到va_list被定義成char*,有一些平臺或操做系統定義爲void*.再
看va_start的定義,定義爲&v+_INTSIZEOF(v),而&v是固定參數在堆棧的
地址,因此咱們運行va_start(ap, v)之後,ap指向第一個可變參數在堆
棧的地址,如圖:
高地址|-----------------------------|
|函數返回地址 |
|-----------------------------|
|....... |
|-----------------------------|
|第n個參數(第一個可變參數) |
|-----------------------------|<--va_start後ap指向
|第n-1個參數(最後一個固定參數)|
低地址|-----------------------------|<-- &v
圖( 1 )
而後,咱們用va_arg()取得類型t的可變參數值,以上例爲int型爲例,我
們看一下va_arg取int型的返回值:
j= ( *(int*)((ap += _INTSIZEOF(int))-_INTSIZEOF(int)) );
首先ap+=sizeof(int),已經指向下一個參數的地址了.而後返回
ap-sizeof(int)的int*指針,這正是第一個可變參數在堆棧裏的地址
(圖2).而後用*取得這個地址的內容(參數值)賦給j.
高地址|-----------------------------|
|函數返回地址 |
|-----------------------------|
|....... |
|-----------------------------|<--va_arg後ap指向
|第n個參數(第一個可變參數) |
|-----------------------------|<--va_start後ap指向
|第n-1個參數(最後一個固定參數)|
低地址|-----------------------------|<-- &v
圖( 2 )
最後要說的是va_end宏的意思,x86平臺定義爲ap=(char*)0;使ap再也不
指向堆棧,而是跟NULL同樣.有些直接定義爲((void*)0),這樣編譯器不
會爲va_end產生代碼,例如gcc在linux的x86平臺就是這樣定義的.
在這裏你們要注意一個問題:因爲參數的地址用於va_start宏,所
以參數不能聲明爲寄存器變量或做爲函數或數組類型.
關於va_start, va_arg, va_end的描述就是這些了,咱們要注意的
是不一樣的操做系統和硬件平臺的定義有些不一樣,但原理倒是類似的.
(三)可變參數在編程中要注意的問題
由於va_start, va_arg, va_end等定義成宏,因此它顯得很愚蠢,
可變參數的類型和個數徹底在該函數中由程序代碼控制,它並不能智能
地識別不一樣參數的個數和類型.
有人會問:那麼printf中不是實現了智能識別參數嗎?那是由於函數
printf是從固定參數format字符串來分析出參數的類型,再調用va_arg
的來獲取可變參數的.也就是說,你想實現智能識別可變參數的話是要通
過在本身的程序裏做判斷來實現的.
另外有一個問題,由於編譯器對可變參數的函數的原型檢查不夠嚴
格,對編程查錯不利.若是simple_va_fun()改成:
void simple_va_fun(int i, ...)
{
va_list arg_ptr;
char *s=NULL;
va_start(arg_ptr, i);
s=va_arg(arg_ptr, char*);
va_end(arg_ptr);
printf("%d %s\n", i, s);
return;
}
可變參數爲char*型,當咱們忘記用兩個參數來調用該函數時,就會出現
core dump(Unix) 或者頁面非法的錯誤(window平臺).但也有可能不出
錯,但錯誤倒是難以發現,不利於咱們寫出高質量的程序.
如下提一下va系列宏的兼容性.
System V Unix把va_start定義爲只有一個參數的宏:
va_start(va_list arg_ptr);
而ANSI C則定義爲:
va_start(va_list arg_ptr, prev_param);
若是咱們要用system V的定義,應該用vararg.h頭文件中所定義的
宏,ANSI C的宏跟system V的宏是不兼容的,咱們通常都用ANSI C,因此
用ANSI C的定義就夠了,也便於程序的移植.
va_list-----申請這麼一個資源
va_start---將資源初始化
va_arg----獲得資源裏的變量值
va_end----釋放資源 數組
------------------------------------------------------------------------------------------------------------------------------------函數
語法:spa
#include <stdarg.h>
type va_arg( va_list argptr, type );
void va_end( va_list argptr );
void va_start( va_list argptr, last_parm );
功能:
宏
va_arg()
用於給函數傳遞可變長度的參數列表。
- 首先,必須調用va_start() 傳遞有效的參數列表va_list和函數強制的第一個參數。第一個參數表明將要傳遞的參數的個數。
- 其次,調用va_arg()傳遞參數列表va_list 和將被返回的參數的類型。va_arg()的返回值是當前的參數。
- 再次,對全部的參數重複調用va_arg()
最後,調用va_end()傳遞va_list對完成後的清除是必須的。操作系統
va_arg()
Syntax:
#include <stdarg.h>
type va_arg( va_list argptr, type );
void va_end( va_list argptr );
void va_start( va_list argptr, last_parm );
Description:
The va_arg() macros are used to pass a variable number of arguments to a function.
1. First, you must have a call to va_start() passing a valid va_list and the mandatory first argument of the function. This first argument describes the number of parameters being passed.
2. Next, you call va_arg() passing the va_list and the type of the argument to be returned. The return value of va_arg() is the current parameter.
3. Repeat calls to va_arg() for however many arguments you have.
4. Finally, a call to va_end() passing the va_list is necessary for proper cleanup.
Example:
int sum( int, ... );
int main( void ) {
int answer = sum( 4, 4, 3, 2, 1 );
printf( "The answer is %d\n", answer );
return( 0 );
}
int sum( int num, ... ) {
int answer = 0;
va_list argptr;
va_start( argptr, num );
for( ; num > 0; num-- )
answer += va_arg( argptr, int );
va_end( argptr );
return( answer );
}
OUTPUT: Displays 10, which is 4+3+2+1.