天天看點

C 語言的可變參數類型 stdarg

va_list/va_start/va_arg/va_end這幾個宏,都是用于函數的可變參數的。

我們來看看在vs2008中,它們是怎麼定義的:

1:  ///stdarg.h

   2:  #define va_start _crt_va_start

   3:  #define va_arg _crt_va_arg

   4:  #define va_end _crt_va_end

   5:   

   6:  ///vadefs.h

   7:  #define _ADDRESSOF(v)   ( &reinterpret_cast<const char &>(v) )

   8:  typedef char *  va_list;

   9:  #define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )

  10:  #define _crt_va_start(ap,v)  ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )

  11:  #define _crt_va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )

  12:  #define _crt_va_end(ap)      ( ap = (va_list)0 )
           
再看看各個宏的功能是什麼?      
  • va_list用于聲明一個變量,我們知道函數的可變參數清單其實就是一個字元串,是以va_list才被聲明為字元型指針,這個類型用于聲明一個指向參數清單的字元型指針變量,例如:va_list ap;//ap:arguement pointer
  • va_start(ap,v),它的第一個參數是指向可變參數字元串的變量,第二個參數是可變參數函數的第一個參數,通常用于指定可變參數清單中參數的個數。
  • va_arg(ap,t),它的第一個參數指向可變參數字元串的變量,第二個參數是可變參數的類型。
  • va_end(ap) 用于将存放可變參數字元串的變量清空(指派為NULL).
1:  /*

   2:  *

   3:  *功能: 宏va_arg()用于給函數傳遞可變長度的參數清單。 

   4:  *首先,必須調用va_start() 傳遞有效的參數清單va_list和函數強制的第一個參數。第一個參數代表将要傳遞的參數的個數。 

   5:  *其次,調用va_arg()傳遞參數清單va_list 和将被傳回的參數的類型。va_arg()的傳回值是目前的參數。 

   6:  *再次,對所有的參數重複調用va_arg() 

   7:  *最後,調用va_end()傳遞va_list對完成後的清除是必須的。 

   8:  *

   9:  *時間:2011年8月17日22:34:04

  10:  *作者:張超

  11:  *Email:[email protected]

  12:  *

  13:  */

  14:   

  15:   

  16:  #include "X:\程式設計練習\C-C++\global.h"

  17:   

  18:  #if va_arg==stdon

  19:  #include <stdio.h>

  20:  #include <stdarg.h>

  21:  #include <stdlib.h>

  22:   

  23:  //第一個參數指定了參數的個數

  24:  int sum(int number,...)

  25:  {

  26:      va_list vaptr;

  27:      int i;

  28:      int sum = 0;

  29:      va_start(vaptr,number);

  30:      for(i=0; i<number;i++)

  31:      {

  32:          sum += va_arg(vaptr,int);

  33:      }

  34:      va_end(vaptr);

  35:      return sum;

  36:  }

  37:   

  38:   

  39:  int main()

  40:  {

  41:      printf("%d\n",sum(4,4,3,2,1));

  42:      system("pause");

  43:      return 0;

  44:  }

  45:   

  46:  #endif
           
  • va_start的功能是要把,ap指針指向可變參數的第一個參數位置處,

    #define _crt_va_start(ap,v) ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )

   先取第一個參數的位址,在sum函數中就是取number的位址并且将其轉化為char *的(因為char *的指針進行加減運算後,偏移的位元組數才與加的數字相同, 如果為int *p,那麼p+1實際上将p移動了4個位元組),然後加上4(__INITSIZEOF(number)=(4+3)&~3),這樣就将ap指向了可變參數字元串的第一個參數。

#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )

  • 以int所占的位元組為标準進行對其操作。
  • 如果int占四位元組,則以四位元組對齊為标準讀取資料。
  • va_arg是要從ap中取下一個參數。

       #define _crt_va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )

對于這個宏,哥糾結了很久,最後終于搞清楚了,究其原因就是自己C語言功底不紮實,具體表現在沒有搞清楚指派表達式的值是怎麼運作的。      
我們看這個宏,首先是ap = ap + __INTSIZEOF(t)。注意到,此時ap已經被改變了,它已經指向了下一個參數,我們令x=ap + __INTSIZEOF(t);      
那麼括号内就變成了(x – __INTSIZEOF(t)),但是這裡沒有指派運算符,是以ap的值沒有發生變化,此時ap仍然指向的是目前參數的下一個參數的位置,      
也就是說ap指向的位置比目前正在處理的位置超前了一個位置。      
其實寫成下面的形式就簡單明了了:      

    #define   va_arg(ap,t)   (*(t   *)((ap   +=   _INTSIZEOF(t)),   ap   -   _INTSIZEOF(t))   )

分析:為什麼要将ap指向目前處理參數的下一個參數了?      
經過上面的分析,我們知道va_start(ap,v)已經将ap指向了可變參數清單的第一個參數了,以後我們每一步操作都需要将ap移動到下一個      
參數的位置,由于我們每次使用可變參數的順序是:va_start(ap,v)—>va_arg(ap,t);這樣我們在第一次去參數的時候,其實ap已經指向了      
第二個參數開始的位置,是以我們用表達式的方式獲得一個指向第一個參數的臨時指針,這樣我們就可以采用這種一緻的方式來處理可變參數清單。