va_list原理及用法
分類: 編程2010-10-20 11:22 1426人閱讀 評論(1) 收藏 舉報
VA_LIST 是在C語言中解決變參問題的一組宏,變參問題是指參數的個數不定,可以是傳入一個參數也可以是多個;可變參數中的每個參數的類型可以不同,也可以相同;可變參數的每個參數並沒有實際的名稱與之相對應,用起來是很靈活。
下面是va_list的用法示例 :
#include <stdarg.h>
int AveInt(int,...);
void main()
{
printf("%d/t",AveInt(2,2,3));
printf("%d/t",AveInt(4,2,4,6,8));
return;
}
int AveInt(int v,...)
{
int ReturnValue=0;
int i=v;
va_list ap ;
va_start(ap,v);
while(i>0)
{
ReturnValue+=va_arg(ap,int) ;
i--;
}
va_end(ap);
return ReturnValue/=v;
}
VA_LIST的用法:
(1)首先在函數裏定義一具VA_LIST型的變量,這個變量是指向參數的指針;
(2)然後用VA_START宏初始化變量剛定義的VA_LIST變量;
(3)然後用VA_ARG返回可變的參數,VA_ARG的第二個參數是你要返回的參數的類型(如果函數有多個可變參數的,依次調用VA_ARG獲取各個參數);
(4)最後用VA_END宏結束可變參數的獲取。
上面是va_list的具體用法,下面講解一下va_list各個語句含義(如上示例黑體部分)和va_list的實現。
可變參數是由宏實現的,但是由於硬件平臺的不同,編譯器的不同,宏的定義也不相同,下面是VC6.0中x86平臺的定義 :
typedef char * va_list; // TC中定義爲void*
#define _INTSIZEOF(n) ((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1) ) //爲了滿足需要內存對齊的系統
#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) ) //ap指向第一個變參的位置,即將第一個變參的地址賦予ap
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) /*獲取變參的具體內容,t爲變參的類型,如有多個參數,則通過移動ap的指針來獲得變參的地址,從而獲得內容*/
#define va_end(ap) ( ap = (va_list)0 ) //清空va_list,即結束變參的獲取
C語言的函數形參是從右向左壓入堆棧的,以保證棧頂是第一個參數,而且x86平臺內存分配順序是從高地址到低地址。因此似函數AVEInt(int var1,int var2,...,int varN)內存分配大致上是這樣的:(可變參數在中間)
棧區:
|棧頂 低地址
|第一個參數var1 <-- &v
|第二個參數var2 <-- va_start(ap,v)後ap指向地址
|...
|函數的最後varN
|...
|函數的返回地址
|...
|棧底 高地址
va_list ap ; 定義一個va_list變量ap
va_start(ap,v) ;執行ap = (va_list)&v + _INTSIZEOF(v),ap指向參數v之後的那個參數的地址,即 ap指向第一個可變參數在堆棧的地址。
va_arg(ap,t) , ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )取出當前ap指針所指的值,並使ap指向下一個參數。 ap+= sizeof(t類型),讓ap指向下一個參數的地址。然後返回ap-sizeof(t類型)的t類型*指針,這正是第一個可變參數在堆棧裏的地址。然後 用*取得這個地址的內容。
va_end(ap) ; 清空va_list ap。
使用VA_LIST應該注意的問題:
(1)因爲va_start, va_arg, va_end等定義成宏,所以它顯得很愚蠢,可變參數的類型和個數完全在該函數中由程序代碼控制,它並不能智能地識別不同參數的個數和類型. 也就是說,你想實現智能識別可變參數的話是要通過在自己的程序裏作判斷來實現的.
(2)另外有一個問題,因爲編譯器對可變參數的函數的原型檢查不夠嚴格,對編程查錯不利.不利於我們寫出高質量的代碼。
(3)由於參數的地址用於VA_START宏,所以參數不能聲明爲寄存器變量,或作爲函數或數組類型。