c語言可變參數列表處理

函數參數的傳遞原理
    函數參數在內存中是以棧的形式存取,從右至左入棧。
    參數在內存中存放格式:
        在進程中,堆棧地址是從高到低分配的。當執行一個函數的時候,將參數列表入棧,壓入堆棧的高地址部分,然後入棧函數的返回地址,接着入棧函數的執行代碼,這個入棧過程,堆棧地址不斷遞減。
總之,函數在堆棧中的分佈情況是,地址從高到低,依次是:函數參數列表,函數返回地址,函數執行代碼段。堆棧中,各個函數的分佈情況是倒敘的。即最後一個參數在列表中地址最高部分,第一個參數在列表地址的最低部分。參數在堆棧中的
分佈情況如下:最後一個參數->倒數第二個參數->...->第一個參數->函數返回地址->函數代碼段.
    宏定義:
        typedef char* va_list
        #define va_start(ap,v)(ap=(va_list)&v+_INTSIZEOF(v))
        #define va_arg(ap,t)(*(t*)((ap +=_INTSIZEOF(t))-_INTSIZEOF(t)))
        #define va_end(ap) ( ap = (va_list)0)
        va_list 是一個字符指針,可以理解爲指向當前參數的一個指針,取參必須通過這個指針進行。調用步驟如下:
            1.在調用參數表之前,定義一個 va_list 類型的變量,(假設va_list 類型變量被定義爲ap);
            2.然後應該對ap進行初始化,讓它指向可變參數表裏面的第一個參數,這是通過 va_start 來實現的,第一個參數是 ap 本身,第二個參數是在變參表前面緊挨着的一個變量,即“...”之前的那個參數;
            3.然後是獲取參數,調用va_arg,它的第一個參數是ap,第二個參數是要獲取的參數的指定類型,然後返回這個指定類型的值,並且把 ap 的位置指向變參表的下一個變量位置;
            4.獲取所有的參數之後,我們有必要將這個 ap 指針關掉,以免發生危險,方法是調用 va_end,他是輸入的參數 ap 置爲 NULL,應該養成獲取完參數表之後關閉指針的習慣。說白了,就是讓我們的程序具有健壯性。通常va_start和va_end是成對出現
    
    各個宏的功能:
        va_list 用於聲明一個變量,我們知道函數的可變參數列表其實就是一個字符串,所以va_list才被聲明爲字符型指針,這個類型用於聲明一個指向參數列表的字符型指針變量,例如:va_list ap;//ap:arguement pointer
        va_start(ap,v) 它的第一個參數是指向可變參數字符串的變量,第二個參數是可變參數函數的第一個參數,通常用於指定可變參數列表中參數的個數。
        va_arg(ap,t) 它的第一個參數指向可變參數字符串的變量,第二個參數是可變參數的類型。
        va_end(ap) 用於將存放可變參數字符串的變量清空(賦值爲NULL).
    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平臺就是這樣定義的。

    例如:

1.
#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, int);#類型不能爲char、signed char、unsigned char、short、unsigned short、signed short、short int、signed short int、unsigned short int、float
           if (strcmp(para, "") == 0 )
               break;
           printf("Parameter #%d is: %s\n", argno, para);
           argno++;
  }
  va_end( argp );
  //將argp置爲NULL
  return 0;
}
int main()
{
   demo("DEMO", "This", "is", "a", "demo!", "");
   return 0;
}


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

//第一個參數指定了參數的個數
int sum(int number,...)
{

  va_list vaptr;
  int i;
  int sum = 0;
  va_start(vaptr,number);
  for(i=0; i<number;i++)
  {
    sum += va_arg(vaptr,int); #類型不能爲char、signed char、unsigned char、short、unsigned short、signed short、short int、signed short int、unsigned short int、float
  }
  va_end(vaptr);
  return sum;
}

int main()
{

  printf("%d\n",sum(4,4,3,2,1));
  return 0;

}

注意:
    1.因爲va_start, va_arg, va_end等定義成宏,可變參數的類型和個數完全在該函數中由程序代碼控制,它並不能智能地識別不同參數的個數和類型,也就是說,你想實現智能識別可變參數的話是要通過在自己的程序裏作判斷來實現。
    2.va_arg(ap,type)取出一個參數的時候,type絕對不能爲以下類型:char、signed char、unsigned char、short、unsigned short、signed short、short int、signed short int、unsigned short int、float。在C語言中,調用一個不帶原型聲明的函數時:調用者會對每個參數執行“默認實際參數提升(default argument promotions)”。同時,對可變長參數列表超出最後一個有類型聲明的形式參數之後的每一個實際參數,也將執行上述提升工作。提升工作如下:a.float類型的實際參數將提升到double;b.char、short和相應的signed、unsigned類型的實際參數提升到int;c.如果int不能存儲原值,則提升到unsigned int。然後,調用者將提升後的參數傳遞給被調用者。所以,my_printf是絕對無法接收到上述類型的實際參數的。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章