可變參數表函數的原理及實現(printf實現)

來源:互聯網
上載者:User

沒事的時候,想一下printf,居然發現都不知道是怎麼樣的大概的原理。找到一篇文章,不過也加上一些自己的理解,寫出此文。

原文地址: http://www.cnblogs.com/liyou_blog/archive/2010/09/01/1814663.html

ANSI C中庫函數printf的正確形式如下:

  int printf(char *fmt, ...);

其中,省略符號表示參數表的數量和類型是可變的。省略符號只能出現在參數表的尾部。如何?這樣一個具有可變參數表的函數?在《The C programme Language》中實現了這樣一個樣本函數:

  void minprintf(char *fmt, ...);

先看看它的實現方式,然後研究下它的實現原理,函數minprintf只處理格式字串和參數,格式轉換則通過調用函數printf實現。

代碼#include "stdarg.h"#include "stdio.h"void minprintf(char *fmt, ...){    va_list ap; //依次指向每個無名參數 //不要怕,va_list是一個宏,後面會說到,還有va_start, va_arg 和 va_end    char *p, *sval;    int ival;    double dval;    va_start(ap,fmt); //將ap指向第一個無名參數    for (p = fmt; *p; p++)    {        if (*p != '%')         {            putchar(*p);            continue;        }        switch( *++p )        {            case 'd':                ival = va_arg(ap,int);                printf("%d",ival);                break;            case 'f':                dval = va_arg(ap,double);                printf("%f",dval);                break;            case 's':                for (sval = va_arg(ap,char *); *sval; sval++)                putchar(*sval);                break;            default:                putchar(*p);                break;        }    }    va_end(ap);}

上述代碼中標準標頭檔“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 其實是一個字元指標類型,代碼中聲明一個變數。該變數將依次引用各參數。代碼中聲明此類型的變數為ap, 即“參數指標”。宏va_start將ap初始化為指向第一個無名參數的指標。第一個參數是 ap 本身,第二個參數是在變參表前面緊挨著的一個變數,即“...”之前的那個參數;在使用ap之前,該宏必須被調用一次。參數表必須至少包括一個有名參數(即“char *fmt”),va_start將最後一個有名參數作為起點。

  然後調用va_arg擷取參數,它的第一個參數是ap,第二個參數是要擷取的參數的指定類型,然後返回這個指定類型的值,並且把 ap 的位置指向變參表的下一個變數位置;
  最後擷取所有的參數之後,我們有必要將這個 ap 指標關掉,以免發生問題,方法是調用 va_end,它將輸入的參數 ap 置為 NULL。

 

函數參數的傳遞原理

函數參數是以資料結構以棧的形式存取,從右至左依次入棧。參數存放在記憶體的棧空間中,在執行函數的時候,從最後一個開始入棧。棧底高地址,棧頂低地址,舉例如下:
對於函數 void foo(int x, float y, char z);
那麼,調用函數的時候,實參 char z 先進棧,然後是 float y,最後是 int x,因此在記憶體中變數的存放次序是 x->y->z(從低地址到高地址的順序),因此,只要知道任意一個變數的地址,並且知道其他變數的類型(這樣才能正確的實現指標移位元運算),可以順藤摸瓜找到其他的輸入變數。一段驗證性的範例程式碼如下,輸出會為1,2,3,4,

代碼void fun(int a, ...){    int i;    int *temp = &a;    temp++;    for (i=0 ; i<a; ++i)    {        printf("%d ,",*temp);        temp++;    }}int main(){    int a = 1;    int b = 2;    int c = 3;    int d = 4;    fun(4,a,b,c,d);    return 0;}

最後,利用可變參數表,實現一個實踐中使用的函數:

int max(int nNum, ...){va_list ap;va_start(ap, nNum);//初始化apint maxNum = va_arg(ap, int);//得到最大的第一個資料int num;for(int i = 1; i < nNum; i++)//{num = va_arg(ap, int); //得到下一個元素if(num > maxNum){maxNum = num;}}va_end(ap);return maxNum;}int main(){printf("%d", max(5, 20, 11, 23, 5, 10));}

程式運行輸出 :  

23

注意:

    由於宏定義來實現變長參數表,因此可能會出現這麼一些問題:

   一. 由於是宏實現,不會對參數類型進行檢查,參數很容易發生隱式的類型轉換擷取一個值(譬如輸入一個float,
    卻以int型去類型轉化),這樣做會出現一些未曾預計的結果。
   二. 參數指標移動時容易出現越界訪問,超出函數棧幀的實際範圍,可能導致程式崩潰的運行時結果。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.