日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
淺談C/C++中可變參數(shù)的原理

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

專注于為中小企業(yè)提供做網(wǎng)站、網(wǎng)站建設(shè)服務(wù),電腦端+手機(jī)端+微信端的三站合一,更高效的管理,為中小企業(yè)石城免費(fèi)做網(wǎng)站提供優(yōu)質(zhì)的服務(wù)。我們立足成都,凝聚了一批互聯(lián)網(wǎng)行業(yè)人才,有力地推動了上1000家企業(yè)的穩(wěn)健成長,幫助中小企業(yè)通過網(wǎng)站建設(shè)實現(xiàn)規(guī)模擴(kuò)充和轉(zhuǎn)變。

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

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

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

于是我們可以自己寫可變參數(shù)的函數(shù)了, 代碼如下:

 
 
 
 
  1. int Sum(int nCount, )
  2. {
  3.     int nSum = 0;
  4.     int* p = &nCount;
  5.     for(int i=0; i
  6.     {
  7.         cout << *(++p) << endl;
  8.         nSum += *p;
  9.     }
  10.     cout << "Sum:" << nSum << endl << endl;
  11.     return nSum;
  12. }
  13. string  SumStr(int nCount, )
  14. {
  15.     string str;
  16.     int* p = &nCount;
  17.     for(int i=0; i
  18.     {
  19.         char* pTemp = (char*)*(++p);
  20.         cout <<  pTemp << endl;
  21.         str += pTemp;
  22.     }
  23.     cout << "SumStr:" << str << endl;
  24.     return str;
  25. }

在我們的測試函數(shù)中nCount表示后面可變參數(shù)的個數(shù),int Sum(int nCount, [[94242]])會打印后面的可變參數(shù)Int值,并且進(jìn)行累加;string  SumStr(int nCount, [[94242]]) 會打印后面可變參數(shù)字符串內(nèi)容,并連接所有字符串。

然后用下面代碼進(jìn)行測試:int main()

 
 
 
 
  1. {
  2.     Sum(3, 10, 20, 30);
  3.     SumStr(5, "aa", "bb", "cc", "dd", "ff");
  4.     
  5.     system("pause");
  6.     return 0;
  7. }

測試結(jié)果如下:

可以看到,我們上面的實現(xiàn)有硬編碼的味道,也有沒有做字節(jié)對齊,為此系統(tǒng)專門給我們封裝了一些支持可變參數(shù)的宏:

  
 
 
 
  1. //typedef char *  va_list;
  2. //#define _ADDRESSOF(v)   ( &reinterpret_cast(v) )
  3. //#define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
  4. //#define _crt_va_start(ap,v)  ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )
  5. //#define _crt_va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
  6. //#define _crt_va_end(ap)      ( ap = (va_list)0 )
  7. //#define va_start _crt_va_start
  8. //#define va_arg _crt_va_arg
  9. //#define va_end _crt_va_end
  10. //#define _ADDRESSOF(v)   ( &reinterpret_cast(v) )
  11. //#define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
  12. //#define _crt_va_start(ap,v)  ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )
  13. //#define _crt_va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
  14. //#define _crt_va_end(ap)      ( ap = (va_list)0 )
  15. //#define va_start _crt_va_start
  16. //#define va_arg _crt_va_arg
  17. //#define va_end _crt_va_end

用系統(tǒng)的這些宏,我們的代碼可以這樣寫了:

 
 
 
 
  1. //use va_arg, praram is int
  2. int SumNew(int nCount, )
  3. {
  4.     int nSum = 0;
  5.     va_list vl = 0;
  6.     va_start(vl, nCount);
  7.     for(int i=0; i
  8.     {
  9.         int n = va_arg(vl, int);
  10.         cout << n << endl;
  11.         nSum += n;
  12.     }
  13.     va_end(vl);
  14.     cout << "SumNew:" << nSum << endl << endl;
  15.     return nSum;
  16. }
  17. //use va_arg,  praram is char*
  18. string SumStrNew(int nCount, )
  19. {
  20.     string str;
  21.     va_list vl = 0;
  22.     va_start(vl, nCount);
  23.     for(int i=0; i
  24.     {
  25.         char* p = va_arg(vl, char*);
  26.         cout <<  p << endl;
  27.         str += p;
  28.     }
  29.     cout << "SumStrNew:" << str << endl << endl;
  30.     return str;
  31. }

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

用下面的代碼進(jìn)行測試:

 
 
 
 
  1. int main() 
  2. {
  3.     Sum(3, 10, 20, 30);
  4.     SumStr(5, "aa", "bb", "cc", "dd", "ff");
  5.     
  6.     SumNew(3, 1, 2, 3);
  7.     SumStrNew(3, "12", "34", "56");
  8.     system("pause");
  9.     return 0;
  10. }

結(jié)果如下:

我們上面的例子傳的可變參數(shù)都是4字節(jié)的, 如果我們的可變參數(shù)傳的是一個結(jié)構(gòu)體,結(jié)果會怎么樣呢?

下面的例子我們傳的可變參數(shù)是std::string

  
 
 
 
  1. //use va_arg,  praram is std::string
  2. void SumStdString(int nCount, )
  3. {
  4.     string str;
  5.     va_list vl = 0;
  6.     va_start(vl, nCount);
  7.     for(int i=0; i
  8.     {
  9.         string p = va_arg(vl, string);
  10.         cout <<  p << endl;
  11.         str += p;
  12.     }
  13.     cout << "SumStdString:" << str << endl << endl;
  14. }
  15. int main() 
  16. {
  17. Sum(3, 10, 20, 30);
  18. SumStr(5, "aa", "bb", "cc", "dd", "ff");
  19. SumNew(3, 1, 2, 3);
  20. SumStrNew(3, "12", "34", "56");
  21. string s1("hello ");
  22. string s2("world ");
  23. string s3("!");
  24. SumStdString(3, s1, s2, s3);
  25. system("pause");
  26. return 0;
  27. }

運(yùn)行結(jié)果如下:

可以看到即使傳入的可變參數(shù)是std::string, 依然可以正常工作。

我們可以反匯編下看看這種情況下的參數(shù)傳遞過程:

很多時候編譯器在傳遞類對象時,即使是傳值,也會在堆棧上通過push對象地址的方式來傳遞,但是上面顯然沒有這么做,因為它要滿足可變參數(shù)的調(diào)用約定,

另外,可以看到最后在調(diào)用sumStdString后,由add esp, 58h來外部清棧。

一個std::string大小是28, 58h = 88 = 28 + 28 + 28 + 4.

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

總的來說可變參數(shù)給我們提供了很高的靈活性和方便性,但是也給會造成不確定性,降低我們程序的安全性,很多時候可變參數(shù)數(shù)量或類型不匹配,就會造成一些不容察覺的問題,只有更好的理解它背后的原理,我們才能更好的駕馭它。


新聞名稱:淺談C/C++中可變參數(shù)的原理
網(wǎng)頁網(wǎng)址:http://www.5511xx.com/article/dpdedpi.html