《C語(yǔ)言接口與實(shí)現(xiàn)》實(shí)驗(yàn)——可變參數(shù)表的使用(va_list, va_start, va_arg, va_end)
《C語(yǔ)言接口與實(shí)現(xiàn)》作為接口庫(kù),源文件中大量使用了可變參數(shù)表,這些到底是怎么使用的?先來(lái)看這幾個(gè)例子,基本明白了可變參數(shù)表使用。后面部分從網(wǎng)上整理了原理:
源程序:
#include#include#include// //?使用示例1:追加串 //? void?Va_Fn1(char?*dest,?char?*data,?...) { va_list?ap; char?*p?=?data; //指向第一個(gè)可變參數(shù) //第二個(gè)參數(shù)就是寫(xiě)?...?前面那個(gè) va_start(ap,?data); //遍歷每一個(gè)可變參數(shù),取出來(lái)使用 while(1) { //訪問(wèn)當(dāng)前這個(gè)可變參數(shù),先用,后遍歷!! strcat(dest,?p); p?=?va_arg(ap,?char?*); if?(p?==?NULL)?break; } //結(jié)束 va_end(ap); } // //?使用示例2:累加和 //? int?Va_Fn2(int?a,?...) { int?ret?=?a; //指向第一個(gè)參數(shù) int?sum?=?0; va_list?ap; //第二個(gè)參數(shù)就是寫(xiě)?...?前面那個(gè) va_start(ap,?a); //遍歷每一個(gè)可變參數(shù),取出來(lái)使用 while(1) { //先用,后遍歷 sum?+=?ret; ret?=va_arg(ap,?int); if?(ret?==?-1)?break; } //結(jié)束 va_end(ap); return?sum; } // //?使用示例3:使用數(shù)據(jù)結(jié)構(gòu) //? typedef?struct { int?x; int?y; }MY_TYPE; void?Va_Fn3(int?n,?MY_TYPE?*p,?...) { int?i?=?0; va_list?ap; MY_TYPE?*tmp?=?p; //第二個(gè)參數(shù)就是寫(xiě)?...?前面那個(gè) va_start(ap,?p); for(;?ix,?tmp->y); tmp?=?va_arg(ap,?MY_TYPE?*); } //結(jié)束 va_end(ap); } // //?使用示例4:稍復(fù)雜的可變參數(shù)表 // (char?*,?int,?int), (char?*,?int,?int),?...... //? void?Va_Fn4(char?*msg,?...) { va_list?ap; int?i,?j; char?*str?=?msg; //指向第一個(gè)參數(shù) //第二個(gè)參數(shù)就是寫(xiě)?...?前面那個(gè) va_start(ap,?msg); while(1) { //使用可變參數(shù)表,先使用 i?=?va_arg(ap,?int); j?=?va_arg(ap,?int); printf("t%s---%d----%dn",?str,?i,?j); //后遍歷 str?=?va_arg(ap,?char?*); //第二個(gè)參數(shù)是【可變參數(shù)】的類型 if?(str?==?NULL)?break; } //結(jié)束 va_end(ap); } void?main() { //示例1 char?dest[1000]?=?{0}; Va_Fn1(dest,?"Hello?",?"OK?",?"歡迎?",?"Yes?",?NULL); printf("示例1?=?%sn",?dest); //示例2 int?x?=?Va_Fn2(9,?9,?1,?3,?90,?-1); printf("示例2?=?%dn",?x); //示例3 MY_TYPE?a,?b,?c; a.x?=?100; a.y?=?300; b.x?=?1100; b.y?=?1300; c.x?=?6100; c.y?=?6300; printf("示例3:n"); Va_Fn3(3,?&a,?&b,?&c); //示例4 printf("示例4:n"); Va_Fn4("Hello",?1,?2,?"XYZ",?300,?600,?"ABC",?77,?88,?NULL); }
輸出:
示例1?=?Hello?OK?歡迎?Yes 示例2?=?112 示例3: ????????100-----300 ????????1100-----1300 ????????6100-----6300 示例4: ????????Hello---1----2 ????????XYZ---300----600 ????????ABC---77----88 Press?any?key?to?continue
原理:
1. 函數(shù)參數(shù)是以數(shù)據(jù)結(jié)構(gòu):棧的形式存取,從右至左入棧
2.?首先是參數(shù)的內(nèi)存存放格式:參數(shù)存放在內(nèi)存的堆棧段中,在執(zhí)行函數(shù)的時(shí)候,從最后一個(gè)開(kāi)始入棧。因此棧底高地址,棧頂?shù)偷刂?,舉個(gè)例子如下:
void func(int x, float y, charz);
那么,調(diào)用函數(shù)的時(shí)候,實(shí)參char z 先進(jìn)棧,然后是 float y,最后是 intx,因此在內(nèi)存中變量的存放次序是 x->y->z,因此,從理論上說(shuō),我們只要探測(cè)到任意一個(gè)變量的地址,并且知道其他變量的類型,通過(guò)指針移位運(yùn)算,則總可以順藤摸瓜找到其他的輸入變量。<----這就是原理!!
3. 看源碼(vc98/include/stdarg.h):(注意是X86相關(guān)的,不是mips,不是ALPHA的,不是PPC等等的?。?/p>
typedef char * ?va_list;
#ifdef ?_M_IX86
#define _INTSIZEOF(n) ? ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
#define va_start(ap,v) ?( ap = (va_list)&v + _INTSIZEOF(v) )
#define va_arg(ap,t) ? ?( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define va_end(ap) ? ? ?( ap = (va_list)0 )
#elif ? defined(_M_MRX000)
這里有復(fù)雜的宏,展開(kāi)它:在“工程屬性” —〉“C/C++”—〉“Project Options” 手工填入/P,然后rebuild,會(huì)產(chǎn)生于.cpp同名的.i文件,里面的宏被展開(kāi)了。來(lái)看展開(kāi)后的第一個(gè)函數(shù):
void?Va_Fn1(char?*dest,?char?*data,?...) { va_list?ap; char?*p?=?data; (?ap?=?(va_list)&data?+?(?(sizeof(data)?+?sizeof(int)?-?1)?&?~(sizeof(int)?-?1)?)?); while(1) { strcat(dest,?p); p?=?(?*(char?*?*)((ap?+=?(?(sizeof(char?*)?+?sizeof(int)?-?1)?&?~(sizeof(int)?-?1)?))?-?(?(sizeof(char?*)?+?sizeof(int)?-?1)?&?~(sizeof(int)?-?1)?))?); if?(p?==?0)?break; } (?ap?=?(va_list)0?); }
再看展開(kāi)的第二個(gè)函數(shù),這個(gè)較簡(jiǎn)單:
int?Va_Fn2(int?a,?...) { int?ret?=?a; int?sum?=?0; va_list?ap; (?ap?=?(va_list)&a?+?(?(sizeof(a)?+?sizeof(int)?-?1)?&?~(sizeof(int)?-?1)?)?); while(1) { sum?+=?ret; ret?=(?*(int?*)((ap?+=?(?(sizeof(int)?+?sizeof(int)?-?1)?&?~(sizeof(int)?-?1)?))?-?(?(sizeof(int)?+?sizeof(int)?-?1)?&?~(sizeof(int)?-?1)?))?); if?(ret?==?-1)?break; } (?ap?=?(va_list)0?); return?sum; }