《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ù)】的類(lèi)型
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è)變量的地址,并且知道其他變量的類(lèi)型,通過(guò)指針移位運(yùn)算,則總可以順藤摸瓜找到其他的輸入變量。<----這就是原理?。?/p>
3. 看源碼(vc98/include/stdarg.h):(注意是X86相關(guān)的,不是mips,不是ALPHA的,不是PPC等等的!)
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;
}




