《C语言接口与实现》实验——可变参数表的使用(va_list, va_start, va_arg, va_end)

来源:互联网 发布:大型游戏下载软件 编辑:程序博客网 时间:2024/05/18 16:59

《C语言接口与实现》作为接口库,源文件中大量使用了可变参数表,这些到底是怎么使用的?先来看这几个例子,基本明白了可变参数表使用。后面部分从网上整理了原理:

源程序:

#include <stdio.h>#include <stdarg.h>#include <string.h>//// 使用示例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(; i<n; i++){printf("\t%d-----%d\n", tmp->x, 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----%d\n", str, i, j);//后遍历str = va_arg(ap, char *);//第二个参数是【可变参数】的类型if (str == NULL) break;}//结束va_end(ap);}void main(){//示例1char dest[1000] = {0};Va_Fn1(dest, "Hello ", "OK ", "欢迎 ", "Yes ", NULL);printf("示例1 = %s\n", dest);//示例2int x = Va_Fn2(9, 9, 1, 3, 90, -1);printf("示例2 = %d\n", x);//示例3MY_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);//示例4printf("示例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----88Press 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;}


原创粉丝点击