• <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

            文章均收錄自他人博客,但不喜標(biāo)題前加-[轉(zhuǎn)貼],因其丑陋,見(jiàn)諒!~
            隨筆 - 1469, 文章 - 0, 評(píng)論 - 661, 引用 - 0
            數(shù)據(jù)加載中……

            從printf談可變參數(shù)函數(shù)的實(shí)現(xiàn)

            摘要:一直以來(lái)都覺(jué)得printf似乎是c語(yǔ)言庫(kù)中功能最強(qiáng)大的函數(shù)之一,不僅因?yàn)樗芨袷交敵觯谟谒膮?shù)個(gè)數(shù)沒(méi)有限制,要幾個(gè)就給幾個(gè),來(lái)者不拒。printf這種對(duì)參數(shù)個(gè)數(shù)和參數(shù)類型的強(qiáng)大適應(yīng)性,讓人產(chǎn)生了對(duì)它進(jìn)行探索的濃厚興趣。

            關(guān)鍵字:printf, 可變參數(shù)

            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的使用情況來(lái)看,我們不難發(fā)現(xiàn)一個(gè)規(guī)律,就是無(wú)論其可變的參數(shù)有多少個(gè),printf的第一個(gè)參數(shù)總是一個(gè)字符串。而正是這第一個(gè)參數(shù),使得它可以確認(rèn)后面還有有多少個(gè)參數(shù)尾隨。而尾隨的每個(gè)參數(shù)占用的棧空間大小又是通過(guò)第一個(gè)格式字符串確定的。然而printf到底是怎樣取第一個(gè)參數(shù)后面的參數(shù)值的呢,請(qǐng)看如下代碼

            2. printf 函數(shù)的實(shí)現(xiàn)

             

            //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. 分析

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

             

                va_start(ap, A)

             

                {

             

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

             

                }

             

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

             

                void fun(double d,...)

             

                {

             

                    va_list args;

             

                    int n;

             

                    va_start(args, d);

             

                }

             

            va_start(args, d);相當(dāng)于

             

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

             

               此時(shí)args正好指向d后面的第一個(gè)參數(shù)。

              可變參數(shù)函數(shù)的實(shí)現(xiàn)與函數(shù)調(diào)用的棧結(jié)構(gòu)有關(guān),正常情況下c/c 的函數(shù)參數(shù)入棧規(guī)則為__stdcall, 它是從右到左的,即函數(shù)中的最右邊的參數(shù)最先入棧。對(duì)于函數(shù)

             

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

             

                {

             

                    int d;

             

                    ...

             

                }

             

            其棧結(jié)構(gòu)為

             

                0x1ffc-->d

             

                0x2000-->a

             

                0x2004-->b

             

                0x2008-->c

             

               對(duì)于任何編譯器,每個(gè)棧單元的大小都是sizeof(int), 而函數(shù)的每個(gè)參數(shù)都至少要占一個(gè)棧單元大小,如函數(shù) void fun1(char a, int b, double c, short d) 對(duì)一個(gè)32的系統(tǒng)其棧的結(jié)構(gòu)就是

             

                0x1ffc-->a  (4字節(jié))

             

                0x2000-->b  (4字節(jié))

             

                0x2004-->c  (8字節(jié))

             

                0x200c-->d  (4字節(jié))

             

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

              如果知道了參數(shù)a的地址,則要取后續(xù)參數(shù)的值則可以通過(guò)a的地址計(jì)算a后面參數(shù)的地址,然后取對(duì)應(yīng)的值,而后面參數(shù)的個(gè)數(shù)可以直接由變量a指定,當(dāng)然也可以像printf一樣根據(jù)第一個(gè)參數(shù)中的%模式個(gè)數(shù)來(lái)決定后續(xù)參數(shù)的個(gè)數(shù)和類型。如果參數(shù)的個(gè)數(shù)由第一個(gè)參數(shù)a直接決定,則后續(xù)參數(shù)的類型如果沒(méi)有變化并且是已知的,則我們可以這樣來(lái)取后續(xù)參數(shù), 假定后續(xù)參數(shù)的類型都是double;

             

            void fun1(int num, ...)

             

            {

             

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

             

                double Param1 = *p;

             

                double Param2 = *(p 1);

             

                ...

             

                double Paramn  *(p num);

             

            }

             

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

             

            i---int

             

            s---signed short

             

            l---long

             

            c---char

             

            "ild"模式用于表示后續(xù)有三個(gè)參數(shù),按順序分別為int, long, double類型的三個(gè)參數(shù)那么這樣我們可以定義自己版本的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函數(shù),它返回多個(gè)輸入整型參數(shù)的最大值

             

            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;

             

            }

             

            可以這樣調(diào)用, 后續(xù)參數(shù)的個(gè)數(shù)由第一個(gè)參數(shù)指定

             

            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);

             

            結(jié)論

               對(duì)于可變參數(shù)函數(shù)的調(diào)用有一點(diǎn)需要注意,實(shí)際的可變參數(shù)的個(gè)數(shù)必須比前面模式指定的個(gè)數(shù)要多,或者不小于,也即后續(xù)參數(shù)多一點(diǎn)不要緊,但不能少, 如果少了則會(huì)訪問(wèn)到函數(shù)參數(shù)以外的堆棧區(qū)域,這可能會(huì)把程序搞崩掉。前面模式的類型和后面實(shí)際參數(shù)的類型不匹配也有可能造成把程序搞崩潰,只要模式指定的數(shù)據(jù)長(zhǎng)度大于后續(xù)參數(shù)長(zhǎng)度,則這種情況就會(huì)發(fā)生。如:

             

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

             

               參數(shù)1234的默認(rèn)類型為整型,而模式指定的需要為double型,其數(shù)據(jù)長(zhǎng)度比int大,這種情況就有可能訪問(wèn)函數(shù)參數(shù)堆棧以外的區(qū)域,從而造成危險(xiǎn)。但是printf("%d, %d, %d", 1.0, 20., 3.0);這種情況雖然結(jié)果可能不正確,但是確不會(huì)造成災(zāi)難性后果。因?yàn)閷?shí)際指定的參數(shù)長(zhǎng)度比要求的參數(shù)長(zhǎng)度長(zhǎng),堆棧不會(huì)越界。

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

            評(píng)論

            # re: 從printf談可變參數(shù)函數(shù)的實(shí)現(xiàn)  回復(fù)  更多評(píng)論   

            錯(cuò)誤百出,建議還是別看了,真正的還是沒(méi)說(shuō)到點(diǎn)子.浪費(fèi)時(shí)間!
            2011-04-29 13:35 |
            国产亚洲精品美女久久久| 久久免费视频1| 亚洲欧美一级久久精品| 天天躁日日躁狠狠久久 | 色综合久久中文字幕无码| 青青青青久久精品国产h| 性做久久久久久久久老女人| 日韩av无码久久精品免费| 99国内精品久久久久久久| 久久精品视频一| 伊人久久综在合线亚洲2019| 久久久久久久97| 国产成人99久久亚洲综合精品| 久久精品国产免费观看三人同眠| 免费观看久久精彩视频| 奇米影视7777久久精品人人爽 | 色狠狠久久AV五月综合| 精品一久久香蕉国产线看播放| 亚洲精品高清国产一线久久 | 色成年激情久久综合| 久久精品国产日本波多野结衣| 国产高清美女一级a毛片久久w| 亚洲狠狠婷婷综合久久久久| 久久亚洲av无码精品浪潮| 久久国产一区二区| 日韩精品久久久久久免费| 中文字幕久久亚洲一区| 91久久成人免费| MM131亚洲国产美女久久| 区亚洲欧美一级久久精品亚洲精品成人网久久久久 | 久久精品人人做人人爽电影蜜月| 亚洲精品无码专区久久同性男| 香蕉久久一区二区不卡无毒影院 | 91精品国产9l久久久久| A级毛片无码久久精品免费| 欧美午夜A∨大片久久 | 日本久久久精品中文字幕| 欧美黑人激情性久久| 精品国产乱码久久久久久呢| 久久人人爽人人精品视频| AA级片免费看视频久久|