C語言可變參數(shù)詳解_第1頁
C語言可變參數(shù)詳解_第2頁
C語言可變參數(shù)詳解_第3頁
C語言可變參數(shù)詳解_第4頁
C語言可變參數(shù)詳解_第5頁
已閱讀5頁,還剩12頁未讀 繼續(xù)免費閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領(lǐng)

文檔簡介

1、C語言可變參數(shù)詳解1,首先,怎么得到參數(shù)的值。對于一般的函數(shù),我們可以通過參數(shù)對應(yīng)在參數(shù)列表里的標(biāo)識符來得到。但是參數(shù)可變函數(shù)那些可變的參數(shù)是沒有參數(shù)標(biāo)識符的,它只有“”,所以通過標(biāo)識符來得到是不可能的,我們只有另辟途徑。我們知道函數(shù)調(diào)用時都會分配??臻g,而函數(shù)調(diào)用機制中的棧結(jié)構(gòu)如下圖所示:|返回地址|調(diào)用函數(shù)運行狀態(tài)|可見,參數(shù)是連續(xù)存儲在棧里面的,那么也就是說,我們只要得到可變參數(shù)的前一個參數(shù)的地址,就可以通過指針訪問到那些可變參數(shù)。但是怎么樣得到可變參數(shù)的前一個參數(shù)的地址呢?不知道你注意到?jīng)]有,參數(shù)可變函數(shù)在可變參數(shù)之前必有一個參數(shù)是固定的,并使用標(biāo)識符,而且通常被聲明為char*類型,

2、printf函數(shù)也不例外。這樣的話,我們就可以通過這個參數(shù)對應(yīng)的標(biāo)識符來得到地址,從而訪問其他參數(shù)變得可能。我們可以寫一個測試程序來試一下:#includevoidva_test(char*fmt,);/參數(shù)可變的函數(shù)聲明voidmain()inta=1,c=55;charb=b;va_test(,a,b,c);/用四個參數(shù)做測試voidva_test(char*fmt,)參數(shù)可變的函數(shù)定義,注意第一個參數(shù)為char*fmtchar*p=NULL;|p=(char*)&fmt;/注意不是指向fmt,而是指向&fmt,并且強制轉(zhuǎn)化為char*,以便一個一個字節(jié)訪問for(inti=0;i2 .L

3、inuxman手冊;3 .x86匯編,還有一些安全編碼方面的資料。轉(zhuǎn)帖對C/C+可變參數(shù)表的深層探索C/C+語言有一個不同于其它語言的特性,即其支持可變參數(shù),典型的函數(shù)如printf、scanf等可以接受數(shù)量不定的參數(shù)。如:printf(Iloveyou);printf(%d,a);printf(%d,%d,a,b);第一、二、三個printf分別接受1、2、3個參數(shù),讓我們看看printf函數(shù)的原型:intprintf(constchar*format,);從函數(shù)原型可以看出,其除了接收一個固定的參數(shù)format以外,后面的參數(shù)用表示。在C/C+語言中,表示可以接受不定數(shù)量的參數(shù),理論上來講

4、,可以是0或0以上的n個參數(shù)本文將對C/C+可變參數(shù)表的使用方法及C/C+支持可變參數(shù)表的深層機理進行探索一.可變參數(shù)表的用法1、相關(guān)宏標(biāo)準C/C+包含頭文件stdarg.h,該頭文件中定義了如下三個宏:voidva_start(va_listarg_ptr,prev_param);/返回值:求得的最大整數(shù)*/int max ( int num, .)int m = -0x7FFFFFFF; /* 32 系統(tǒng)中最小的整數(shù) */ va_list ap;va_start ( ap, num );for ( int i= 0; i num; i+ )/ .可以同數(shù)字一起顯示,需設(shè)置標(biāo)志(d、l、x、

5、s)/extern void DrawText ( BYTE xPos, BYTE yPos, LPBYTE lpStr, .)BYTE lpData100; / 緩沖區(qū) BYTE byIndex;ANSIversion*/typeva_arg(va_listarg_ptr,type);voidva_end(va_listarg_ptr);在這些宏中,va就是variableargument(可變參數(shù))的意思;arg_ptr是指向可變參數(shù)表的指針;prevparam則指可變參數(shù)表的前一個固定參數(shù);type為可變參數(shù)的類型。valist也是一個宏,其定義為typedefchar*valist,實

6、質(zhì)上是一char型指針。char型指針的特點是+、操作對其作用的結(jié)果是增1和減1(因為sizeof(char)為1),與之不同的是int等其它類型指針的+、-操作對其作用的結(jié)果是增sizeof(type)或減sizeof(type),而且sizeof(type)大于1。通過va_start宏我們可以取得可變參數(shù)表的首指針,這個宏的定義為:#defineva_start(ap,v)(ap=(va_list)&v+_INTSIZEOF(v)顯而易見,其含義為將最后那個固定參數(shù)的地址加上可變參數(shù)對其的偏移后賦值給ap,這樣ap就是可變參數(shù)表的首地址。其中的INTSIZEOF宏定義為:#define_

7、INTSIZEOF(n)(sizeof(n)+sizeof(int)-1)&(sizeof(int)-1)va_arg宏的意思則指取出當(dāng)前arg_ptr所指的可變參數(shù)并將ap指針指向下一可變參數(shù),其原型為:#defineva_arg(list,mode)(mode*)(list=(char*)(int)list+(_builtin_alignof(mode)=4?3:7)&(_builtin_alignof(mode)=4?-4:-8)+sizeof(mode)-1對這個宏的具體含義我們將在后面深入討論。而va_end宏被用來結(jié)束可變參數(shù)的獲取,其定義為:#defineva_end(list)

8、可以看出,va_end(list)實際上被定義為空,沒有任何真實對應(yīng)的代碼,用于代碼對稱,與vastart對應(yīng);另外,它還可能發(fā)揮代碼的自注釋”作用。所謂代碼的自注釋,指的是代碼能自己注釋自己。下面我們以具體的例子來說明以上三個宏的使用方法。2、一個簡單的例子#include/*函數(shù)名:max*功能:返回n個整數(shù)中的最大值*參數(shù):num:整數(shù)的個數(shù):num個輸入的整數(shù)intt=va_arg(ap,int);if(tm)m=t;va_end(ap);returnm;/*主函數(shù)調(diào)用max*/intmain(intargc,char*argv口)intn=max(5,5,6,3,8,5);/*求5個

9、整數(shù)中的最大值*/coutn;return0;函數(shù)max中首先定義了可變參數(shù)表指針ap,而后通過va_start(ap,num)取得了參數(shù)表首地址(賦給了ap),其后的for循環(huán)則用來遍歷可變參數(shù)表。這種遍歷方式與我們在數(shù)據(jù)結(jié)構(gòu)教材中經(jīng)??吹降谋闅v方式是類似的。函數(shù)max看起來簡潔明了,但是實際上printf的實現(xiàn)卻遠比這復(fù)雜。max函數(shù)之所以看起來簡單,是因為:(1) max函數(shù)可變參數(shù)表的長度是已知的,通過num參數(shù)傳入;(2) max函數(shù)可變參數(shù)表中參數(shù)的類型是已知的,都為int型。而printf函數(shù)則沒有這么幸運。首先,printf函數(shù)可變參數(shù)的個數(shù)不能輕易的得到,而可變參數(shù)的類型也不

10、是固定的,需由格式字符串進行識別(由f、d、s等確定),因此則涉及到可變參數(shù)表的更復(fù)雜應(yīng)用。下面我們以實例來分析可變參數(shù)表的高級應(yīng)用。2 .高級應(yīng)用下面這個程序是我們?yōu)槟城度胧较到y(tǒng)(該系統(tǒng)中CPU的字長為16位)編寫的在屏幕上顯示格式字符串的函數(shù)DrawText,它的用法類似于intprintf(constchar*format,.)函數(shù),但其輸出的目標(biāo)為嵌入式系統(tǒng)的液晶顯示屏幕(LED)。/函數(shù)名稱:DrawText/功能說明:在顯示屏上繪制文字/參數(shù)說明:xPos-橫坐標(biāo)的位置0.30/yPos縱坐標(biāo)的位置0.64BYTEbyLen;DWORDdwTemp;WORDwTemp;inti;v

11、a_listIpParam;memset(IpData,0,100);byLen=strlen(lpStr);byIndex=0;|va_start(lpParam,lpStr);for(i=0;ibyLen;i+)if(lpStri!=喔是格式符開始lpDatabyIndex+=lpStri;elseswitch(lpStri+1)/整型cased:caseU:wTemp=va_arg(lpParam,int);byindex+=IntToStr(IpData+bylndex,(DWORD)wTemp);i+;break;/長整型casel:caseL:dwTemp=va_arg(lpPar

12、am,long);byindex+=IntToStr(lpData+byindex,(DWORD)dwTemp);i+;break;/16進制(長整型)casex:caseX:dwTemp=va_arg(lpParam,long);byindex+=HexToStr(lpData+bylndex,(DWORD)dwTemp);i+;break;default:lpDatabyindex+=lpStri;|break;va_end(IpParam);lpDatabyindex=0;DisplayString(xPos,yPos,IpData,TRUE);/在屏幕上顯示字符串IpData在這個函數(shù)

13、中,需通過對傳入的格式字符串(首地址為IpStr)進行識別來獲知可變參數(shù)個數(shù)及各個可變參數(shù)的類型,具體實現(xiàn)體現(xiàn)在for循環(huán)中。譬如,在識別為d后,做的是va_arg(IpParam,int),而獲知為l和x后則進行的是vaarg(IpParam,long)。格式字符串識別完成后,可變參數(shù)也就處理完了。一在項目的最初,我們一直苦于不能找到一個好的辦法來混合輸出字符串和數(shù)字,我們采用了分別顯示數(shù)字和字符串的方法,并分別指定坐標(biāo),程序條理被破壞。而且,在混合顯示的時候,要給各類數(shù)據(jù)分別人工計算坐標(biāo),我們感覺頭疼不已。以前的函數(shù)為:/顯示字符串showString(BYTExPos,BYTEyPos,

14、LPBYTEIpStr)顯示數(shù)字showNum(BYTExPos,BYTEyPos,intnum)|以16進制方式顯示數(shù)字_|showHexNum(BYTExPos,BYTEyPos,intnum)最終,我們用DrawText(BYTExPos,BYTEyPos,LPBYTEIpStr,.)函數(shù)代替了原先所有的輸出函數(shù),程序得到了簡化。就這樣,兄弟們用得爽翻了。3 .運行機制探索通過第2節(jié)我們學(xué)會了可變參數(shù)表的使用方法,相信喜歡拋根問底的讀者還不甘心,必然想知道如下問題:(1)為什么按照第2節(jié)的做法就可以獲得可變參數(shù)并對其進行操作?_(2)C/C+在底層究竟是依靠什么來對這一語法進行支持的,為

15、什么其它語言就不能提供可變參數(shù)表呢?我們帶著這些疑問來一步步進行摸索。3.1調(diào)用機制反匯編反匯編是研究語法深層特性的終極良策,先來看看2.2節(jié)例子中主函數(shù)進行max(5,5,6,3,8,5)調(diào)用時的反匯編:1.004010c8push52. 004010CApush83. 004010CCpush34. 004010CEpush65. 004010D0push56. 004010D2push57. 004010D4callILT+5(max)(0040100a)從上述反匯編代碼中我們可以看出,C/C+函數(shù)調(diào)用的過程中:第一步:將參數(shù)從右向左入棧(第16行);第二步:調(diào)用call指令進行跳轉(zhuǎn)(第

16、7行)。這兩步包含了深刻的含義,它說明C/C+默認的調(diào)用方式為由調(diào)用者管理參數(shù)入棧的操作,且入棧的順序為從右至左,這種調(diào)用方式稱為_cdecl調(diào)用。x86系統(tǒng)的入棧方向為從高地址到低地址,故第1至n個參數(shù)被放在了地址遞增的堆棧內(nèi)。在被調(diào)用函數(shù)內(nèi)部,讀取這些堆棧的內(nèi)容就可獲得各個參數(shù)的值,讓我們反匯編到max函數(shù)的內(nèi)部:intmax(intnum,)1.00401020pushebp2. 00401021movebp,esp3. 00401023subesp,50h4. 00401026pushebx5. 00401027pushesi6. 00401028pushedi7. 00401029l

17、eaedi,ebp-50h8. 0040102Cmovecx,14h9. 00401031moveax,0CCCCCCCCh10. 00401036repstosdwordptrediva_listap;intm=-0x7FFFFFFF;/*32系統(tǒng)中最小的整數(shù)*/11. 00401038movdwordptrebp-8,80000001hva_start(ap,num);12. 0040103Fleaeax,ebp+0Ch13. 00401042movdwordptrebp-4,eaxfor(inti=0;im)28. 00401071moveax,dwordptrt29. 00401074

18、cmpeax,dwordptrebp-830. 00401077jlemax+5Fh(0040107f)m=t;31.00401079movecx,dwordptrt32. 0040107Cmovdwordptrebp-8,ecx33. 0040107Fjmpmax+2Eh(0040104e)va_end(ap);34. 00401081movdwordptrebp-4,0|returnm;35. 00401088moveax,dwordptrebp-836. 0040108Bpopedi37. 0040108Cpopesi38. 0040108Dpopebx39. 0040108Emovesp,ebp40. 00401090popebp41. 00401091ret分析上述反匯編代碼,對于一個真正的程序員而言,將是一種很大的享受;而對于初學(xué)者,也將使其受益良多。所以請一定要賴著頭皮認真研究,千萬不要被嚇倒!行110進行執(zhí)行函數(shù)內(nèi)代碼的準備工作,保存現(xiàn)場。第2行對堆棧進行移動;第3行則意味著max函數(shù)為其內(nèi)部局部變量準備的堆??臻g為50h字節(jié);第11行表示把變量n的內(nèi)存空間安排在了函數(shù)內(nèi)部局部棧底減8的位置(占用4個字節(jié))。|第1213行非常關(guān)鍵,對應(yīng)著va_start(ap,num),這兩行將第一個可變參數(shù)的

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

最新文檔

評論

0/150

提交評論