一、可變參數表介紹
c/c++語言具備一個不同于其他編程語言的的特性,即支持可變參數。
例如C庫中的printf,scanf等函數,都支持輸入數量不定的參數。例如:
printf("helloworld");////1個參數 prinf("%d",?a);?????????////2個參數 printf("%d,?%d",?a,?b);?////3個參數
printf函數原型為 int printf(const char *format, …);
從printf的原型來看,其除了接受一個固定參數format以外,后面的參數使用…來表示。
在c/c++語言中,…表示可以接受不定數量的參數。
二、可變參數表用法
在標準C/C++中,頭文件中定義了如下三個宏:
voidva_start(va_listarg_ptr,prev_param);/*ANSIversion*/ typeva_arg(va_listarg_ptr,type); voidva_end(va_listarg_ptr);
va 就是variable argument(可變參數)的意思
arg_ptr 是指向可變參數表的指針
prev_param 則指可變參數表的前一個固定參數
type 為可變參數的類型
va_list 也是一個宏
其定義為typedef char * va_list 實質上是一char 型指針。
char 型指針的特點是++、--操作對其作用的結果是增1 和減1(因為sizeof(char)為1)。
與之不同的是int 等其它類型指針的++、--操作對其作用的結果是增sizeof(type)或減sizeof(type),而且sizeof(type)大于1。
通過使用va_start宏我們可以取得可變參數表的首指針,這個宏的定義為:
#defineva_start(ap,v)(ap=(va_list)&v+_INTSIZEOF(v))
其作用為將最后那個固定參數的地址加上可變參數對其的偏移后賦值給ap,這樣ap就是可變參數表的首地址。
_INTSIZEOF 宏定義為:
#define_INTSIZEOF(n)((sizeof(n)+sizeof(int)–1)&~(sizeof(int)–1))
宏定義va_arg原型為:
#defineva_arg(list,mode)((mode*)(list= (char*)((((int)list+(__builtin_alignof(mode)<=4?3:7))?& (__builtin_alignof(mode)<=4?-4:-8))+sizeof(mode))))[-1]
其作用為指取出當前arg_ptr 所指的可變參數并將ap 指針指向下一可變參數。
va_end宏定義用來結束可變參數的獲取,定義為:
#defineva_end(list)
va_end ( list )實際上被定義為空,沒有任何真實對應的代碼,用于代碼對稱,與va_start對應;
可能發揮代碼的“自注釋”作用。所謂代碼的“自注釋”,指的是代碼能自己注釋自己。
三、可變參數表的簡單使用
#include#include #include /** *@brief求n個數中的最大值 *@details *@param[in]num整數個數 *@param[out]...整數 *@retval最大整數 *@par */ intmax(intnum,...){ intm=-0x7FFFFFFF;/*32系統中最小的整數*/ va_listap; va_start(ap,num); for(inti=0;im){ m=t; } } va_end(ap); returnm; } intmain(intargc,char*argv[]){ intn=max(5,5,6,3,8,5);/*求5個整數中的最大值*/ cout<
max(int num, …)中首先定義了可變參數表指針ap,而后通過va_start ( ap, num )取得了參數表首地址(賦給了ap),其后的for 循環則用來遍歷可變參數表。
max函數相比于printf簡單了許多,其原因如下:
max函數可變參數表的長度是已知的,通過num參數傳入;
max函數可變參數表中參數的類型是已知的,都為int型;
printf 函數可變參數的個數不能輕易的得到,而可變參數的類 型也不是固定的,需由格式字符串進行識別(由%f、%d、%s 等確定)。
四、運行機制
反匯編是研究語法深層特性的終極良策,首先查看main函數中調用max函數時的反匯編:
1.004010C8push5 2.004010CApush8 3.004010CCpush3 4.004010CEpush6 5.004010D0push5 6.004010D2push5 7.004010D4call@ILT+5(max)(0040100a)
第一步:將參數從右向左入棧(第1~6行)
第二步:調用call 指令進行跳轉(第7行)
這兩步包含了深刻的含義,它說明C/C++默認的調用方式為由調用者管理參數入棧的操作,且入棧的順序為從右至左,這種調用方式稱為_cdecl調用。
x86系統的入棧方向為從高地址到低地址,故第1至n個參數被放在了地址遞增的堆棧內。在被調用函數內部,讀取這些堆棧的內容就可獲得各個參數的值,讓我們反匯編到max函數的內部。
intmax(intnum,...){ 1.00401020pushebp 2.00401021movebp,esp 3.00401023subesp,50h 4.00401026pushebx 5.00401027pushesi 6.00401028pushedi 7.00401029leaedi,[ebp-50h] 8.0040102Cmovecx,14h 9.00401031moveax,0CCCCCCCCh 10.00401036repstosdwordptr[edi] va_listap; intm=-0x7FFFFFFF;/*32系統中最小的整數*/ 11.00401038movdwordptr[ebp-8],80000001h va_start(ap,num); 12.0040103Fleaeax,[ebp+0Ch] 13.00401042movdwordptr[ebp-4],eax for(inti=0;im) 28.00401071moveax,dwordptr[t] 29.00401074cmpeax,dwordptr[ebp-8] 30.00401077jlemax+5Fh(0040107f) m=t; 31.00401079movecx,dwordptr[t] 32.0040107Cmovdwordptr[ebp-8],ecx } 33.0040107Fjmpmax+2Eh(0040104e) va_end(ap); 34.00401081movdwordptr[ebp-4],0 returnm; 35.00401088moveax,dwordptr[ebp-8] } 36.0040108Bpopedi 37.0040108Cpopesi 38.0040108Dpopebx 39.0040108Emovesp,ebp 40.00401090popebp 41.00401091ret
第1~10行進行執行函數內代碼的準備工作,保存現場;
第2行對堆棧進行移動;
第3行則意味著max函數為其內部局部變量準備的堆棧空間為50h字節;
第11行表示把變量n 的內存空間安排在了函數內部局部棧底減8的位置(占用4個字節);
第12~13行非常關鍵,對應著va_start ( ap, num),這兩行將第一個可變參數的地址賦值給了指針ap;
從第12行可以看出num 的地址為ebp+0Ch;
從第13行可以看出ap 被分配在函數內部局部棧底減4 的位置上(占用4 個字節);
第22~27行最為關鍵,對應著va_arg (ap, int);
第22~24行的作用為將ap 指向下一可變參數(可變參數的地址間隔為4 個字節,從add eax,4 可以看出);
第25~27行則取當前可變參數的值賦給變量t。這段反匯編很奇怪,它先移動可變參數指針,再在賦值指令里面回過頭來取先前的參數值賦給t(從mov edx,dword ptr [ecx-4]語句可以看出);
第36~41行恢復現場和堆棧地址,執行函數返回操作。
審核編輯:湯梓紅
-
C語言
+關注
關注
180文章
7604瀏覽量
136826 -
編程語言
+關注
關注
10文章
1945瀏覽量
34736 -
函數
+關注
關注
3文章
4331瀏覽量
62618 -
C++
+關注
關注
22文章
2108瀏覽量
73651 -
可變參數
+關注
關注
0文章
2瀏覽量
4818
原文標題:C語言可變參數的使用詳解
文章出處:【微信號:mcu168,微信公眾號:硬件攻城獅】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論