《C语言接口与实现》实验——可变参数表的使用(va_list, va_start, va_arg, va_end)
扫描二维码
随时随地手机看文章
《C语言接口与实现》作为接口库,源文件中大量使用了可变参数表,这些到底是怎么使用的?先来看这几个例子,基本明白了可变参数表使用。后面部分从网上整理了原理:
源程序:
#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. 首先是参数的内存存放格式:参数存放在内存的堆栈段中,在执行函数的时候,从最后一个开始入栈。因此栈底高地址,栈顶低地址,举个例子如下:
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; }