可變參數列表與printf()函數的實現


問題

  當我們剛開始學習C語言的時候,就接觸到printf()函數,可是當時“道行”不深或許不夠細心留意,又或者我們理所當然地認為庫函數規定這樣就是這樣,沒有發現這個函數與普通的函數存在區別,普通函數的參數在函數定義的時候就確定,而printf()函數的參數列表在調用時可變。還有一個原因導致我們沒有去關注這個函數的實現,就是在編程的過程中很少用到參數列表可變的函數。的確是這樣的,但是如果可以理解並內化,這將在編程過程中對某些功能實現帶來很大的幫助。比如,在嵌入式設備開發中,可以利用設備的接口(UART、USB)編寫出一套類似printf()函數功能的調試工具。

 

printf()函數的實現

static char sprint_buf[1024];

int printf(char *fmt, ...)

{

  va_list args;

  int n;

 

  va_start(args, fmt);

  n = vsprintf(sprint_buf, fmt, args);

  va_end(args);

  write(stdout, sprint_buf, n);

 

  return n;

}

  咋一看,printf()函數的試下也不是太復雜,有幾個陌生又關鍵的詞語va_list、va_start、va_end、vsprintf。其實還有一個va_arg(stdarg.h),是參數列表可變的關鍵,而真正的打印實現是vsprintf()函數,在這里不討論vsprintf()函數,只討論三個宏定義。

 

先看看stdarg.h文件下這三個宏的實現:

第一種:

typedef char  *va_list;

#define va_start(ap,v)   ap = (va_list)&v + sizeof(v)

#define va_arg(ap,t)      (((t *)ap)++[0])

#define va_end(ap)

 

第二種:

typedef char  *va_list;  

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

#define va_start(ap,v)        ( ap = (va_list)&v + _INTSIZEOF(v) )  

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

#define va_end(ap)             ( ap = (va_list)0 ) 

 

_INTSIZEOF(n):為了字節對齊,將n的長度化為int長度的整數倍。

在32位系統中,~(sizeof(int)–1) )展開為~(4-1)=~(00000011b)=11111100b,這樣任何數& ~(sizeof(int)–1) )后最后兩位肯定為0,也就是4的整數倍。

 

每個平台下的stdarg.h頭文件的定義都不相同,但是意思都一樣:

(1) va_list:    定義一個va_list型的變量ap,也就是char *;

(2) va_start:獲取到可變參數表的首地址,並將該地址賦給指針ap;

(3) va_arg:   獲取當前ap所指向的可變參數,並將指針ap指向下一個可變參數。注意,該宏的第二個參數為類型;

(4) va_end:   結束可變參數的獲取。

 

應用

當理解了printf()函數的實現,便可以着手去實現嵌入式設備中的調試工具,其原理一樣:

int bsp_debug_printf(const char *fmt, ...)

{

  int ret = -1;

 

  va_list ap;

  char buf[256];

 

   va_start(ap, fmt);

   (void)vsnprintf((char *)buf, sizeof(buf), fmt, ap);

  va_end(ap);

  bsp_puts(buf);

  ret = 0;

 

  return  ret;

}

以上只是簡單的實現這個功能,並沒有考慮硬件保護,返回打印字符數等。

 

再看一例:

找出最大值

int MaxTest(int c, ...)  

{  

  int max = c;  

  va_list ap;

                     

   va_start(ap,c);        

 

  c = va_arg(ap,int);    

  while(0 != c)  

  {  

    if(max < c) max = c;  

          c = va_arg(ap,int); 

  } 

  va_end(ap);

                     

  return max;  

}   

  這個例子主要想利用va_list、va_start、va_arg、va_end這四個關鍵指令來實現可變參數列表的函數,它需要一個參數列表末尾標識(0)來告訴系統這參數列表的最后一個參數。

 

還可以有另外一種方法來實現以上功能:

int MaxTest1(int n, ...)

{

       int max = n;

       int *p = &n + 1;

      

       while(0 != *p)

       {

              if (max < *p) max = *p;

              p++;

       }

      

       return max;

}

  這個例子利用了函數參數的存儲規則來實現對參數的獲取。可變參數函數的實現與函數調用的棧結構有關,正常情況下C/C++的函數參數入棧規則從右到左的,即函數中的最右邊的參數最先入棧。

  這種方法看似效果相同,但是稍有不慎就會帶來災難。若printf()函數由這種方法實現,而沒有邊界的檢查,當堆棧越界訪問時極可能導致程序崩潰。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM