1. 程式人生 > >va_start(),va_end()函式應用

va_start(),va_end()函式應用

原理解釋:

VA_LIST 是在C語言中解決變參問題的一組巨集,在<stdarg.h>標頭檔案下。

VA_LIST的用法:
      (1)首先在函式裡定義一具VA_LIST型的變數,這個變數是指向引數的指標
      (2)然後用VA_START巨集初始化變數剛定義的VA_LIST變數,這個巨集的第二個引數是第一個可變引數的前一個參數,是一個固定的引數。
      (3)然後用VA_ARG返回可變的參數,VA_ARG的第二個引數是你要返回的引數的型別。
      (4)最後用VA_END巨集結束可變引數的獲取。然後你就可以在函式裡使用第二個引數了。如果函式有多個可變引數的,依次呼叫VA_ARG獲取各個引數。



VA_LIST在編譯器中的處理:

(1)在執行VA_START(ap,v)以後,ap指向第一個可變引數在堆疊的地址
(2)VA_ARG()取得型別t的可變引數值,在這步操作中首先apt = sizeof(t型別),讓ap指向下一個引數的地址。然後返回ap-sizeof(t型別)的t型別*指標,這正是  第一個可變引數在堆疊裡的地址。然後用*取得這個地址的內容。
(3)VA_END(),X86平臺定義為ap = ((char*)0),使ap不再指向堆疊,而是跟NULL一樣,有些直接定義為((void*)0),這樣編譯器不會為VA_END產生程式碼,例如gcc在Linux的X86平臺就是這樣定義的。

要注意的是:由於引數的地址用於VA_START巨集,所以引數不能宣告為暫存器變數,或作為函式或陣列型別。

使用VA_LIST應該注意的問題:
   (1)因為va_start, va_arg, va_end等定義成巨集,所以它顯得很愚蠢,可變引數的型別和個數完全在該函式中由程式程式碼控制,它並不能智慧地識別不同引數的個數和型別. 也就是說,你想實現智慧識別可變引數的話是要通過在自己的程式裡作判斷來實現的.
   (2)另外有一個問題,因為編譯器對可變引數的函式的原型檢查不夠嚴格,對程式設計查錯不利.不利於我們寫出高質量的程式碼。
 

小結:可變引數的函式原理其實很簡單,而VA系列是以巨集定義來定義的,實現跟堆疊相關。我們寫一個可變函式的C函式時,有利也有弊,所以在不必要的場合,我們無需用到可變引數,如果在C++裡,我們應該利用C++多型性來實現可變引數的功能,儘量避免用C語言的方式來實現。


va_list ap; //宣告一個變數來轉換引數列表   
va_start(ap,fmt);          //初始化變數   
va_end(ap);     //結束變數列表,和va_start成對使用   
可以根據va_arg(ap,type)取出引數  

已經經過除錯成功的輸出程式

#include<stdio.h>
#include <stdarg.h>

#define bufsize 80
char buffer[bufsize];

int vspf(char *fmt, ...)
{
     va_list argptr;
     int cnt;
    va_start(argptr, fmt);

     cnt = vsnprintf(buffer,bufsize ,fmt, argptr);

     va_end(argptr);

    return(cnt);
}

int main(void)
{
     int inumber = 30;

     float fnumber = 90.0;

     char string[4] = "abc";

    vspf("%d %f %s", inumber, fnumber, string);

    printf("%s\n", buffer);

    return 0;
}

執行結果為:

30 90.000000 abc