• <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>
            posts - 311, comments - 0, trackbacks - 0, articles - 0
              C++博客 :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理

            (搬運工)C/C++中可變參數的原理

            Posted on 2012-09-18 14:27 點點滴滴 閱讀(222) 評論(0)  編輯 收藏 引用 所屬分類: 02 編程語言
            以前只是知道可變參數怎么用,但是一直對它的原理是似懂非懂,現在對計算機有了比較深刻的認識之后,回頭再看,豁然開朗。

            要理解可變參數,首先要理解函數調用約定, 為什么只有__cdecl的調用約定支持可變參數,而__stdcall就不支持?

            實際上__cdecl和__stdcall函數參數都是從右到左入棧,它們的區別在于由誰來清棧,__cdecl由外部調用函數清棧,而__stdcall由被調用函數本身清棧, 顯然對于可變參數的函數,函數本身沒法知道外部函數調用它時傳了多少參數,所以沒法支持被調用函數本身清棧(__stdcall), 所以可變參數只能用__cdecll.

            另外還要理解函數參數傳遞過程中堆棧是如何生長和變化的,從堆棧低地址到高地址,依次存儲 被調用函數局部變量,上一函數堆棧楨基址,函數返回地址,參數1, 參數2, 參數3...,相關知識可以參考我的這篇堆棧楨的生成原理

            有了上面的知識,我可以知道函數調用時,參數2的地址就是參數1的地址加上參數1的長度,而參數3的地址是參數2的地址加上參數2的長度,以此類推。

            于是我們可以自己寫可變參數的函數了, 代碼如下:
            int Sum(int nCount, )
            {
            int nSum = 0;
            int* p = &nCount;
            for(int i=0; i<nCount; ++i)
            {
            cout << *(++p) << endl;
            nSum += *p;
            }

            cout << "Sum:" << nSum << endl << endl;
            return nSum;
            }

            string SumStr(int nCount, )
            {
            string str;
            int* p = &nCount;

            for(int i=0; i<nCount; ++i)
            {
            char* pTemp = (char*)*(++p);
            cout << pTemp << endl;
            str += pTemp;
            }

            cout << "SumStr:" << str << endl;
            return str;
            }

            在我們的測試函數中nCount表示后面可變參數的個數,int Sum(int nCount, )會打印后面的可變參數Int值,并且進行累加;string SumStr(int nCount, ) 會打印后面可變參數字符串內容,并連接所有字符串。
            然后用下面代碼進行測試:int main()
            {
            Sum(3, 10, 20, 30);
            SumStr(5, "aa", "bb", "cc", "dd", "ff");

            system("pause");

            return 0;
            }

            測試結果如下:


            可以看到,我們上面的實現有硬編碼的味道,也有沒有做字節對齊,為此系統專門給我們封裝了一些支持可變參數的宏:
            //typedef char * va_list;

            //#define _ADDRESSOF(v) ( &reinterpret_cast<const char &>(v) )
            //#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )

            //#define _crt_va_start(ap,v) ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )
            //#define _crt_va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
            //#define _crt_va_end(ap) ( ap = (va_list)0 )

            //#define va_start _crt_va_start
            //#define va_arg _crt_va_arg
            //#define va_end _crt_va_end

            用系統的這些宏,我們的代碼可以這樣寫了:
            //use va_arg, praram is int
            int SumNew(int nCount, )
            {
            int nSum = 0;
            va_list vl = 0;
            va_start(vl, nCount);

            for(int i=0; i<nCount; ++i)
            {
            int n = va_arg(vl, int);
            cout << n << endl;
            nSum += n;
            }

            va_end(vl);
            cout << "SumNew:" << nSum << endl << endl;
            return nSum;
            }

            //use va_arg, praram is char*
            string SumStrNew(int nCount, )
            {
            string str;
            va_list vl = 0;
            va_start(vl, nCount);

            for(int i=0; i<nCount; ++i)
            {
            char* p = va_arg(vl, char*);
            cout << p << endl;
            str += p;
            }

            cout << "SumStrNew:" << str << endl << endl;
            return str;
            }

            可以看到,其中 va_list實際上只是一個參數指針,va_start根據你提供的最后一個固定參數來獲取第一個可變參數的地址,va_arg將指針指向下一個可變參數然后返回當前值, va_end只是簡單的將指針清0.

            用下面的代碼進行測試:
            int main()
            {
            Sum(3, 10, 20, 30);
            SumStr(5, "aa", "bb", "cc", "dd", "ff");

            SumNew(3, 1, 2, 3);
            SumStrNew(3, "12", "34", "56");

            system("pause");

            return 0;
            }

            結果如下:


            我們上面的例子傳的可變參數都是4字節的, 如果我們的可變參數傳的是一個結構體,結果會怎么樣呢?
            下面的例子我們傳的可變參數是std::string
            //use va_arg, praram is std::string
            void SumStdString(int nCount, )
            {
            string str;
            va_list vl = 0;
            va_start(vl, nCount);

            for(int i=0; i<nCount; ++i)
            {
            string p = va_arg(vl, string);
            cout << p << endl;
            str += p;
            }

            cout << "SumStdString:" << str << endl << endl;
            }

            int main()
            {
            Sum(3, 10, 20, 30);
            SumStr(5, "aa", "bb", "cc", "dd", "ff");
            SumNew(3, 1, 2, 3);
            SumStrNew(3, "12", "34", "56");
            string s1("hello ");
            string s2("world ");
            string s3("!");
            SumStdString(3, s1, s2, s3);
            system("pause");
            return 0;
            }

            運行結果如下:


            可以看到即使傳入的可變參數是std::string, 依然可以正常工作。
            我們可以反匯編下看看這種情況下的參數傳遞過程:

            很多時候編譯器在傳遞類對象時,即使是傳值,也會在堆棧上通過push對象地址的方式來傳遞,但是上面顯然沒有這么做,因為它要滿足可變參數的調用約定,
            另外,可以看到最后在調用sumStdString后,由add esp, 58h來外部清棧。
            一個std::string大小是28, 58h = 88 = 28 + 28 + 28 + 4.

            從上面的例子我們可以看到,對于可變參數的函數,有2種東西需要確定,一是可變參數的數量, 二是可變參數的類型,上面的例子中,參數數量我們是在第一個參數指定的,參數類型我們是自己約定的。這種方式在實際使用中顯然是不方便,于是我們就有了_vsprintf, 我們根據一個格式化字符串的來表示可變參數的類型和數量,比如C教程中入門就要學習printf, sprintf等。

            總的來說可變參數給我們提供了很高的靈活性和方便性,但是也給會造成不確定性,降低我們程序的安全性,很多時候可變參數數量或類型不匹配,就會造成一些不容察覺的問題,只有更好的理解它背后的原理,我們才能更好的駕馭它。
            99久久精品国产毛片| 久久久久AV综合网成人| 久久高潮一级毛片免费| 久久亚洲视频| 国产高潮国产高潮久久久| 久久精品免费大片国产大片| 久久夜色撩人精品国产小说| 久久久无码精品亚洲日韩按摩 | 久久亚洲私人国产精品vA | 亚洲中文字幕久久精品无码APP| 久久综合香蕉国产蜜臀AV| 久久国产成人精品国产成人亚洲| 久久国产免费直播| 久久久久久极精品久久久| 人妻少妇久久中文字幕一区二区| 性做久久久久久免费观看| 久久国产精品-国产精品| 777午夜精品久久av蜜臀| 精品久久久久久99人妻| 久久亚洲精品中文字幕三区| 婷婷五月深深久久精品| 亚洲精品乱码久久久久久蜜桃 | 久久精品国产国产精品四凭| 久久精品中文闷骚内射| 一级A毛片免费观看久久精品| 欧美精品一本久久男人的天堂| 久久这里只有精品18| 亚洲精品乱码久久久久久不卡| 精品久久久久久99人妻| 曰曰摸天天摸人人看久久久| 国内精品久久久久久99蜜桃| 久久久久久国产精品免费无码| 无码人妻精品一区二区三区久久 | 人妻无码αv中文字幕久久琪琪布 人妻无码久久一区二区三区免费 人妻无码中文久久久久专区 | 久久99国产精品99久久| 久久99精品久久久久久动态图| 日韩精品久久久久久久电影蜜臀| 国产精品亚洲综合久久| 久久久久亚洲精品天堂久久久久久 | 久久男人中文字幕资源站| 久久久久18|