由于在C語言中沒有函數重載,解決不定數目函數參數問題變得比較麻煩;即使采用C++,如果參數個數不能确定,也很難采用函數重載.對這種情況,有些人采用指針參數來解決問題.
讀取可變參數的過程其實就是在堆棧中,使用指針,周遊堆棧段中的參數清單,從低位址到高位址一個一個地把參數内容讀出來的過程·
1.在C中,當我們無法列出傳遞函數的所有實參的類型和數目時,可以用省略号指定參數表
void foo(...);
void foo(parm_list,...);
這種方式和我們以前認識的不大一樣,但我們要記住這是C中一種傳參的形式,屬于變長傳參
2.函數參數的傳遞原理
函數參數是以資料結構:棧的形式存取,從右至左入棧。
首先是參數的記憶體存放格式:參數存放在記憶體的堆棧段中,在執行函數的時候,從最後一個開始入棧。
是以棧底高位址,棧頂低位址
例子如下:
void func(int x, float y, char z);
那麼,調用函數的時候,實參 char z 先進棧,然後是 float y,最後是 int x,是以在記憶體中變量的存放次序是 x->y->z,是以,從理論上說,我們隻要探測到任意一個變量的位址,并且知道其他變量的類型,通過指針移位運算,則總可以順藤摸瓜找到其他的輸入變量。
下面是 <stdarg.h> 裡面重要的幾個宏定義如下:
typedef char* va_list;
void va_start ( va_list ap, prev_param ); /* ANSI version */
type va_arg ( va_list ap, type );
void va_end ( va_list ap );
va_list 是一個字元指針,可以了解為指向目前參數的一個指針,取參必須通過這個指針進行。
<Step 1> 在調用參數表之前,定義一個 va_list 類型的變量,(假設va_list 類型變量被定義為ap);
<Step 2> 然後應該對ap 進行初始化,讓它指向可變參數表裡面的第一個參數,這是通過 va_start 來實作的,第一個參數是 ap 本身,第二個參數是在變參表前面緊挨着的一個變量,即“...”之前的那個參數;
<Step 3> 然後是擷取參數,調用va_arg,它的第一個參數是ap,第二個參數是要擷取的參數的指定類型,然後傳回這個指定類型的值,并且把 ap 的位置指向變參表的下一個變量位置;
<Step 4> 擷取所有的參數之後,我們有必要将這個 ap 指針關掉,以免發生危險,方法是調用 va_end,他是輸入的參數 ap 置為 NULL,應 該養成擷取完參數表之後關閉指針的習慣。說白了,就是讓我們的程式具有健壯性。通常va_start和va_end是成對出現。
具體事例:
#include <stdio.h>
#include <stdarg.h>
void arg_test(int i1,int i2, ...);
int main(int argc, char* argv[])
{
int int_size = _INTSIZEOF(int);
printf("int_size=%d\n", int_size);
arg_test(0, 1, 2, 3, 4);
return 0;
}
void arg_test(int i1,int i2, ...)
{
int j=0;
va_list arg_ptr; //定義儲存函數參數的結構
//i最好寫最後一個确定的參數
//因為arg_ptr指向的位址為傳入的參數i位址加4
//例如,下面如果寫成i1,則arg_ptr指向i2,即arg_test(0, 1, 2, 3, 4)參數裡的1
//寫成i2,則arg_ptr指向的是arg_test(0, 1, 2, 3, 4)參數裡的2,即第一個可變參數
//va_start(arg_ptr, i1); //可先嘗試用i1驗證下
va_start(arg_ptr, i2);
printf("&i1 = %p &i2 = %p arg_ptr = %p\n", &i1, &i2, arg_ptr);//列印va_start之後arg_ptr位址
j = *((int *)arg_ptr); //這時arg_ptr應該比參數i的位址高sizeof(int)個位元組,即指向下一個參數的位址
printf("%d %d %d\n", i1, i2, j); //直接列印第一個參數
j = va_arg(arg_ptr, int); //傳回目前指針指向的參數,并把指針指向下一個參數
printf("&i1 = %p &i2 = %p arg_ptr = %p\n", &i1, &i2, arg_ptr);//列印va_start之後arg_ptr位址
printf("%d %d %d\n", i1, i2, j);
j = *((int *)arg_ptr);
printf("&i1 = %p &i2 = %p arg_ptr = %p\n", &i1, &i2, arg_ptr);//列印va_start之後arg_ptr位址
printf("%d %d %d\n", i1, i2, j);
/*這時arg_ptr應該比參數i的位址高sizeof(int)個位元組,即指向下一個參數的位址,如果已經是最後一個參數,arg_ptr會為NULL*/
va_end(arg_ptr); /*将arg_ptr置為NULL*/
}
————————————————
輸出:
int_size=4
&i1 = 0028F8F4 &i2 = 0028F8F8 arg_ptr = 0028F8FC
0 1 2
&i1 = 0028F8F4 &i2 = 0028F8F8 arg_ptr = 0028F900
0 1 2
&i1 = 0028F8F4 &i2 = 0028F8F8 arg_ptr = 0028F900
0 1 3
請按任意鍵繼續. . .