《C語言接口與實現(xiàn)》實驗——可變參數表的使用(va_list, va_start, va_arg, va_end)
《C語言接口與實現(xiàn)》作為接口庫,源文件中大量使用了可變參數表,這些到底是怎么使用的?先來看這幾個例子,基本明白了可變參數表使用。后面部分從網上整理了原理:
源程序:
#include#include#include//
//?使用示例1:追加串
//?
void?Va_Fn1(char?*dest,?char?*data,?...)
{
va_list?ap;
char?*p?=?data; //指向第一個可變參數
//第二個參數就是寫?...?前面那個
va_start(ap,?data);
//遍歷每一個可變參數,取出來使用
while(1)
{
//訪問當前這個可變參數,先用,后遍歷??!
strcat(dest,?p);
p?=?va_arg(ap,?char?*);
if?(p?==?NULL)?break;
}
//結束
va_end(ap);
}
//
//?使用示例2:累加和
//?
int?Va_Fn2(int?a,?...)
{
int?ret?=?a; //指向第一個參數
int?sum?=?0;
va_list?ap;
//第二個參數就是寫?...?前面那個
va_start(ap,?a);
//遍歷每一個可變參數,取出來使用
while(1)
{
//先用,后遍歷
sum?+=?ret;
ret?=va_arg(ap,?int);
if?(ret?==?-1)?break;
}
//結束
va_end(ap);
return?sum;
}
//
//?使用示例3:使用數據結構
//?
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;
//第二個參數就是寫?...?前面那個
va_start(ap,?p);
for(;?ix,?tmp->y);
tmp?=?va_arg(ap,?MY_TYPE?*);
}
//結束
va_end(ap);
}
//
//?使用示例4:稍復雜的可變參數表
// (char?*,?int,?int), (char?*,?int,?int),?......
//?
void?Va_Fn4(char?*msg,?...)
{
va_list?ap;
int?i,?j;
char?*str?=?msg; //指向第一個參數
//第二個參數就是寫?...?前面那個
va_start(ap,?msg);
while(1)
{
//使用可變參數表,先使用
i?=?va_arg(ap,?int);
j?=?va_arg(ap,?int);
printf("t%s---%d----%dn",?str,?i,?j);
//后遍歷
str?=?va_arg(ap,?char?*); //第二個參數是【可變參數】的類型
if?(str?==?NULL)?break;
}
//結束
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. 函數參數是以數據結構:棧的形式存取,從右至左入棧
2.?首先是參數的內存存放格式:參數存放在內存的堆棧段中,在執(zhí)行函數的時候,從最后一個開始入棧。因此棧底高地址,棧頂低地址,舉個例子如下:
void func(int x, float y, charz);
那么,調用函數的時候,實參char z 先進棧,然后是 float y,最后是 intx,因此在內存中變量的存放次序是 x->y->z,因此,從理論上說,我們只要探測到任意一個變量的地址,并且知道其他變量的類型,通過指針移位運算,則總可以順藤摸瓜找到其他的輸入變量。<----這就是原理??!
3. 看源碼(vc98/include/stdarg.h):(注意是X86相關的,不是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)
這里有復雜的宏,展開它:在“工程屬性” —〉“C/C++”—〉“Project Options” 手工填入/P,然后rebuild,會產生于.cpp同名的.i文件,里面的宏被展開了。來看展開后的第一個函數:
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?);
}
再看展開的第二個函數,這個較簡單:
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;
}




