天天看點

va_start和va_end簡單使用

由于在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
請按任意鍵繼續. . .