• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            woaidongmao

            文章均收錄自他人博客,但不喜標題前加-[轉貼],因其丑陋,見諒!~
            隨筆 - 1469, 文章 - 0, 評論 - 661, 引用 - 0
            數據加載中……

            從printf談可變參數函數的實現

            摘要:一直以來都覺得printf似乎是c語言庫中功能最強大的函數之一,不僅因為它能格式化輸出,更在于它的參數個數沒有限制,要幾個就給幾個,來者不拒。printf這種對參數個數和參數類型的強大適應性,讓人產生了對它進行探索的濃厚興趣。

            關鍵字:printf, 可變參數

            1. 使用情形

             

            int a =10;

             

            double b = 20.0;

             

            char *str = "Hello world";

             

            printf("begin print\n");

             

            printf("a=%d, b=%.3f, str=%s\n", a, b, str);

             

            ...

             

              從printf的使用情況來看,我們不難發現一個規律,就是無論其可變的參數有多少個,printf的第一個參數總是一個字符串。而正是這第一個參數,使得它可以確認后面還有有多少個參數尾隨。而尾隨的每個參數占用的棧空間大小又是通過第一個格式字符串確定的。然而printf到底是怎樣取第一個參數后面的參數值的呢,請看如下代碼

            2. printf 函數的實現

             

            //acenv.h

             

            typedef char *va_list;

             

             

             

            #define  _AUPBND        (sizeof (acpi_native_int) - 1)

             

            #define  _ADNBND        (sizeof (acpi_native_int) - 1)

             

                                   

             

            #define _bnd(X, bnd)    (((sizeof (X))   (bnd)) & (~(bnd)))

             

            #define va_arg(ap, T)   (*(T *)(((ap)  = (_bnd (T, _AUPBND))) - (_bnd (T,_ADNBND))))

             

            #define va_end(ap)      (void) 0

             

            #define va_start(ap, A) (void) ((ap) = (((char *) &(A))   (_bnd (A,_AUPBND))))

             

             

             

            //start.c

             

            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;

             

            }

             

             

             

            //unistd.h

             

            static inline long write(int fd, const char *buf, off_t count)

             

            {

             

                    return sys_write(fd, buf, count);

             

            }

             

            3. 分析

              從上面的代碼來看,printf似乎并不復雜,它通過一個宏va_start把所有的可變參數放到了由args指向的一塊內存中,然后再調用vsprintf. 真正的參數個數以及格式的確定是在vsprintf搞定的了。由于vsprintf的代碼比較復雜,也不是我們這里要討論的重點,所以下面就不再列出了。我們這里要討論的重點是va_start(ap, A)宏的實現,它對定位從參數A后面的參數有重大的制導意義。現在把 #define va_start(ap, A) (void) ((ap) = (((char *) &(A)) (_bnd (A,_AUPBND)))) 的含義解釋一下如下:

             

                va_start(ap, A)

             

                {

             

                     char *ap =  ((char *)(&A))   sizeof(A)int類型大小地址對齊

             

                }

             

               在printfva_start(args, fmt)中,fmt的類型為char *, 因此對于一個32為系統 sizeof(char *) = 4, 如果int大小也是32,則va_start(args, fmt);相當于 char *args = (char *)(&fmt) 4; 此時args的值正好為fmt后第一個參數的地址。對于如下的可變參數函數

             

                void fun(double d,...)

             

                {

             

                    va_list args;

             

                    int n;

             

                    va_start(args, d);

             

                }

             

            va_start(args, d);相當于

             

                char *args = (char *)&d   sizeof(double);

             

               此時args正好指向d后面的第一個參數。

              可變參數函數的實現與函數調用的棧結構有關,正常情況下c/c 的函數參數入棧規則為__stdcall, 它是從右到左的,即函數中的最右邊的參數最先入棧。對于函數

             

                void fun(int a, int b, int c)

             

                {

             

                    int d;

             

                    ...

             

                }

             

            其棧結構為

             

                0x1ffc-->d

             

                0x2000-->a

             

                0x2004-->b

             

                0x2008-->c

             

               對于任何編譯器,每個棧單元的大小都是sizeof(int), 而函數的每個參數都至少要占一個棧單元大小,如函數 void fun1(char a, int b, double c, short d) 對一個32的系統其棧的結構就是

             

                0x1ffc-->a  (4字節)

             

                0x2000-->b  (4字節)

             

                0x2004-->c  (8字節)

             

                0x200c-->d  (4字節)

             

              對于函數void fun1(char a, int b, double c, short d)

              如果知道了參數a的地址,則要取后續參數的值則可以通過a的地址計算a后面參數的地址,然后取對應的值,而后面參數的個數可以直接由變量a指定,當然也可以像printf一樣根據第一個參數中的%模式個數來決定后續參數的個數和類型。如果參數的個數由第一個參數a直接決定,則后續參數的類型如果沒有變化并且是已知的,則我們可以這樣來取后續參數, 假定后續參數的類型都是double;

             

            void fun1(int num, ...)

             

            {

             

                double *p = (double *)((&num) 1);

             

                double Param1 = *p;

             

                double Param2 = *(p 1);

             

                ...

             

                double Paramn  *(p num);

             

            }

             

               如果后續參數的類型是變化而且是未知的,則必須通過一個參數中設定模式來匹配后續參數的個數和類型,就像printf一樣,當然我們可以定義自己的模式,如可以用i表示int參數,d表示double參數,為了簡單,我們用一個字符表示一個參數,并由該字符的名稱決定參數的類型而字符的出現的順序也表示后續參數的順序。我們可以這樣定義字符和參數類型的映射表,

             

            i---int

             

            s---signed short

             

            l---long

             

            c---char

             

            "ild"模式用于表示后續有三個參數,按順序分別為int, long, double類型的三個參數那么這樣我們可以定義自己版本的printf 如下

             

            void printf(char *fmt, ...)

             

            {

             

                char s[80] = "";

             

                int paramCount = strlen(fmt);

             

                write(stdout, "paramCount = " , strlen(paramCount = ));

             

                itoa(paramCount,s,10);

             

                write(stdout, s, strlen(s));

             

                char *p = (char *)(&fmt)   sizeof(char *);

             

                int *pi = (int *)p;

             

                for (int i=0; i<paramCount; i  )

             

                {

             

                    char line[80] = "";

             

                    strcpy(line, "param");

             

                    itoa(i 1, s, 10);

             

                    strcat(line, s);

             

                    strcat(line, "=");

             

                    switch(fmt[i])

             

                    {

             

                        case 'i':

             

                        case 's':

             

                            itoa((*pi),s,10);

             

                            strcat(line, s);

             

                            pi  ;

             

                            break;

             

                        case 'c':

             

                            {

             

                                int len = strlen(line);

             

                                line[len] = (char)(*pi);

             

                                line[len 1] = '\0';

             

                            }

             

                            break;

             

                        case 'l':

             

                            ltoa((*(long *)pi),s,10);

             

                            strcat(line, s);

             

                            pi  ;

             

                            break;

             

                        default:

             

                            break;

             

                    }

             

                }

             

            }

             

            也可以這樣定義我們的Max函數,它返回多個輸入整型參數的最大值

             

            int Max(int n, ...)

             

            {

             

                int *p = &n   1;

             

                int ret = *p;

             

                for (int i=0; i<n; i  )

             

                {

             

                    if (ret < *(p   i))

             

                        ret = *(p   i);

             

                }

             

                return ret;

             

            }

             

            可以這樣調用, 后續參數的個數由第一個參數指定

             

            int m = Max(3, 45, 12, 56);

             

            int m = Max(1, 3);

             

            int m = Max(2, 23, 45);

             

             

             

            int first = 34, second = 45, third=5;

             

            int m = Max(5, first, second, third, 100, 4);

             

            結論

               對于可變參數函數的調用有一點需要注意,實際的可變參數的個數必須比前面模式指定的個數要多,或者不小于,也即后續參數多一點不要緊,但不能少, 如果少了則會訪問到函數參數以外的堆棧區域,這可能會把程序搞崩掉。前面模式的類型和后面實際參數的類型不匹配也有可能造成把程序搞崩潰,只要模式指定的數據長度大于后續參數長度,則這種情況就會發生。如:

             

            printf("%.3f, %.3f, %.6e", 1, 2, 3, 4);

             

               參數1234的默認類型為整型,而模式指定的需要為double型,其數據長度比int大,這種情況就有可能訪問函數參數堆棧以外的區域,從而造成危險。但是printf("%d, %d, %d", 1.0, 20., 3.0);這種情況雖然結果可能不正確,但是確不會造成災難性后果。因為實際指定的參數長度比要求的參數長度長,堆棧不會越界。

            posted on 2009-08-12 13:05 肥仔 閱讀(609) 評論(1)  編輯 收藏 引用 所屬分類: C++ 基礎

            評論

            # re: 從printf談可變參數函數的實現  回復  更多評論   

            錯誤百出,建議還是別看了,真正的還是沒說到點子.浪費時間!
            2011-04-29 13:35 |
            中文精品99久久国产| 国产精品久久久久影院色| 久久受www免费人成_看片中文| 伊人久久成人成综合网222| 久久久久久精品成人免费图片| 久久发布国产伦子伦精品| 天天综合久久久网| 久久久www免费人成精品| 亚洲国产精品久久| 精品综合久久久久久98| 婷婷综合久久狠狠色99h| av色综合久久天堂av色综合在| 日本道色综合久久影院| 伊人久久大香线蕉综合Av| 久久黄视频| 一本久久久久久久| 久久久久人妻精品一区二区三区| 久久久久国产精品嫩草影院| 国产精品久久久久久福利69堂| 久久中文字幕人妻熟av女| 久久久久久毛片免费看| 久久91综合国产91久久精品| 国产69精品久久久久9999APGF| 久久人人爽人爽人人爽av| 日本精品久久久久中文字幕| 精品久久久无码人妻中文字幕豆芽| 亚洲欧洲久久av| 人人狠狠综合久久亚洲高清| 国产精品内射久久久久欢欢 | 久久精品国产亚洲7777| 狠狠色噜噜狠狠狠狠狠色综合久久| 亚洲AV无一区二区三区久久| 久久亚洲国产最新网站| 亚洲国产综合久久天堂| 久久人妻少妇嫩草AV蜜桃| 久久se精品一区精品二区国产| 久久99精品国产| 伊人热人久久中文字幕| 久久se精品一区精品二区国产 | 亚洲伊人久久大香线蕉综合图片| 亚洲精品tv久久久久|