va_start和va_end詳解
阿新 • • 發佈:2018-11-04
1. 在C中,當無法列出傳遞函式的所有實參的型別和數目時,可以用省略號指定引數表。例如:
void foo(...);
void foo(parm_list,...);
2. 函式引數的傳遞原理
函式引數是以棧的形式存取,從右至左入棧。
引數的記憶體存放格式:引數存放在記憶體的堆疊段中,在執行函式的時候,從最後一個開始入棧。因此棧底高地址,棧頂低地址,舉個例子如下:
void func(int x, float y, char z);
那麼,呼叫函式的時候,實參 char z 先進棧,然後是 float y,最後是 int x,因此在記憶體中變數的存放次序是 x->y->z。從理論上說,我們只要探測到任意一個變數的地址,並且知道其他變數的型別,通過指標移位運算,則總可以順藤摸瓜找到其他的輸入變數。舉個例子如下:
#include <stdio.h> //獲取引數列表中的所有引數,並列印 void PrintInt(int cnt, ...) { int *temp = &cnt; temp++; for (int i = 0; i < cnt; ++i) { printf("%d\n", *temp); temp++; } } int main(void) { int a = 1; int b = 2; int c = 3; int d = 4; PrintInt(4, a, b, c, d); return 0; }
執行程式後輸出:
1
2
3
4
3. 利用Va_start相關巨集獲取省略號指定的引數
下面是 <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 );
說明:
1)va_list:一個字元指標,可以理解為指向當前引數的一個指標,取參必須通過這個指標進行。
2)va_start:對ap進行初始化,讓ap指向可變引數表裡面的第一個引數。第一個引數是 ap 本身,第二個引數是在變參表前面緊挨著的一個變數,即“...”之前的那個引數;
3)va_arg: 獲取引數。它的第一個引數是ap,第二個引數是要獲取的引數的指定型別。按照指定型別獲取當前引數,返回這個指定型別的值,然後把 ap 的位置指向變參表中下一個變數的位置;
4)va_end:釋放指標,將輸入的引數 ap 置為 NULL。通常va_start和va_end是成對出現。
使用上面的巨集獲取引數的步驟如下:
<Step 1> 定義一個 va_list 型別的變數,(假設va_list 型別變數被定義為ap);<Step 2> 呼叫va_start ,對ap 進行初始化,讓它指向可變引數表裡面的第一個引數。 <Step 3> 獲取引數,並使用引數。 <Step 4> 獲取所有的引數之後,將 ap 指標關掉。 給出一個例子如下:
#include 〈stdio.h〉 #include 〈string.h〉 #include 〈stdarg.h〉 /*ANSI標準形式的宣告方式,括號內的省略號表示可選引數*/ int demo( char msg, ... ) { /*定義儲存函式引數的結構*/ va_list argp; int argno = 0; char para; /*argp指向傳入的第一個可選引數,msg是最後一個確定的引數*/ va_start( argp, msg ); while (1) { para = va_arg( argp, char); if ( strcmp( para, "") == 0 ) break; printf("Parameter #%d is: %s\n", argno, para); argno++; } va_end( argp ); /*將argp置為NULL*/ return 0; } void main( void ) { demo("DEMO", "This", "is", "a", "demo!", ""); }