va_list原理及用法
va_list原理及用法
2010年10月20日 11:22:00 aihao1984 閱讀數:39787 標籤: list編譯器平臺語言x86程式設計 更多
個人分類: 程式設計
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 ; //定義一個指標變數ap,型別是char *,見下面
va_start(ap,v); //指標ap指向了引數v後面的那個引數的地址,這個引數是用.....表示的第一引數,因為
//用....表示的引數叫變參,所以指標ap指向的也就是第一個變參的位置,也就是第一個
//變參的地址賦值給了指標變數ap.
while(i>0)
{
ReturnValue+=va_arg(ap,int) ; //首先獲得指標ap指向的第一個地址的內容(即第一個變參的值)
//這個變參的型別是int型別,(從va_arg(ap, int)中得知的),同時指標ap自動加1指向第二個
//變參的位置(即第二個變參的地址賦值給ap)。
i--;
}
va_end(ap); //這句的含義是: ap = (va_list)0,就是說將數字0賦值給指標變數ap,因為指標ap的
//資料型別是va_list,相當於char *型別,數字0是整形,所以要進行強制型別轉換成
//va_list型別即:ap = (va_list)0才行。目的是清空指向變參的指標va_list,結束變參的獲取。
return ReturnValue/=v; //相當於ReturnValue = 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巨集,所以引數不能宣告為暫存器變數,或作為函式或陣列型別。
本文參考如下文章:
http://hi.baidu.com/kang_liang/blog/item/168c9059a9a1ca2d2934f05f.html
http://fenge.bokee.com/195016.html
http://blog.csdn.net/homer1984/archive/2009/02/02/3859036.aspx