《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()
{
//示例1
char dest[1000] = {0};
Va_Fn1(dest, "Hello ", "OK ", "欢迎 ", "Yes ", NULL);
printf("示例1 = %s\n", dest);
//示例2
int x = Va_Fn2(9, 9, 1, 3, 90, -1);
printf("示例2 = %d\n", 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;
}