可变参数列表解析

可变”参数列表解析

函数其参数在大多数情况下都是确定的,但是在某些时候,其参数却是可变的。今天我就来简单做一下可变参数列表的解析。
什么是可变参数?如果留心的话可以发现,就库函数里面有些大家常用的均可接受1个以上的任意多个参数。
比如printf函数:

下来先看一个简单的例子,使用可变参数,实现函数求未知参数部分n个数的平均值:
#include <stdio.h>
#include<stdarg.h>
int average(int n, ...)
{
	va_list arg;
	int i = 0;
	int sum = 0;
	va_start(arg, n);
	for (i = 0; i < n; i++)
	{
		sum += va_arg(arg, int);
	}
	return sum / n;
	va_end(arg); 
}
int main()
{
	printf("%d\n", average(3, 5, 6, 7));
	printf("%d\n", average(5, 1, 2, 3, 4, 5));
	system("pause");
	return 0;
}
上面的函数参数即为可变参数,其个数为不确定个。
此程序运行结果为:

从average函数内部开始分析,在VS的原码处可以看到:

1.   va_list :   其本身为类型定义: 
typedef  char *  va_list   其意义为: typedef for pointer to list of arguments defined in STDIO.H typedef用于指向STDIO.H中定义的参数列表的指针)

2.   va_start(arg, n) :  其本身为宏,在函数预处理阶段会进行替换:
#define _INTSIZEOF(n)  ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
宏 _INTSIZEOF(n) , 目的在于把sizeof(n)的结果变成至少是sizeof(int)的整倍数,用来在结构中实现按int的倍数对齐。

#define va_start(arg, n)  (arg = (va_list)&n + _INTSIZEOF(n))
宏  va_start(ap,v) ,其意义在于初始化arg,并找到参数n的地址,并由此地址让arg指向未知参数部分的第一个参数即指向参数n其下一个地址。

3.   va_arg(arg, n) :  其本身为宏,在函数预处理阶段会进行替换:
#define va_arg(arg, t)  ( *(t *)((arg += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
宏 va_arg(arg, t),接受两个参数:va_list变量和参数列表中下一个参数的类型。其操作意义在于:
在栈帧空间中,先让arg+=_INTSIZEOF(t),即让arg下移,令arg指向未知参数部分第二个参数的地址;
而后再给(arg+=_INTSIZEOF(t))-4,此时这个表达式即又指向了未知参数部分的第一个参数;
再给其(t *),强制转换为t型指针;
然后解引用,即得到了未知参数部分第一个参数的值。
再进下一次循环运算时,arg此时指向的已是未知参数部分第二个参数。(我上一篇博客《浅谈函数栈帧》中有分析函数传参过程及参数在栈帧中的分布,看过的话可以很容易理解此处代码的意义)

4.  va_end(arg) : 其本身为宏,在函数预处理阶段会进行替换:
#define va_end(arg) ( arg = (va_list)0 )
宏 va_end(arg) 其意义在于在arg完成调用后,令arg为空指针,保证了程序的安全性。

那么,对上面的例子,用宏进行替换后,其运行又是如何?
来看下面代码:
//#define va_start(ap,v) (ap = (va_list)&v + _INTSIZEOF(v))
//#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
//#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
//#define va_end(ap) ( ap = (va_list)0 )

#include <stdio.h>
#include<stdarg.h>
int average(int n, ...)
{
	//va_list arg;
	char * arg;
	int i = 0;
	int sum = 0;
	//va_start(arg, n);
	arg = (char *)&n + 4;
	for (i = 0; i < n; i++)
	{
		//sum += va_arg(arg, int);
		sum += (*(int *)((arg += 4) - 4));
	}
	//va_end(arg); 
	arg = (char*)0;					
return sum / n;}int main(){printf("%d\n", average(3, 5, 6, 7));printf("%d\n", average(5, 1, 2, 3, 4, 5));system("pause");return 0;}

其运行及如果如下:

 针对上面代码再进行分析既有:


利用可变参数模拟实现printf函数:
#include <stdio.h>
#include<stdarg.h>

void dispaly(int num)
{
	if (num > 9)
	{
		dispaly(num / 9);
	}
	putchar(num % 10 + '0');
}

void my_printf(const char* format, ...)
{
	va_list arg;
	va_start(arg, format);
	while (*format != '\0')
	{
		switch (*format)
		{
			case 'd':
			{
				int ret = va_arg(arg, int);
				dispaly(ret);
			}
				break;
			case 's':
			{
				char* str = va_arg(arg, char*);
				while (*str)
				{
					putchar(*str);
					str++;
				}
			}
				break;
			case  'c':
			{
				 char word = va_arg(arg, char);
				 putchar(word);
			}
				break;
			default:
				putchar(*format);
				break;
		}
		format++;
	}
	va_end(arg);
}

int main()
{
	int a = 10;
	char arr[] = "abcdef";
	char c = '1';
	my_printf("s d c", arr, a, c);
	system("pause");
	return 0;
}
可变参数也存在限制,如下:
1. 可变参数必须从头到尾逐个访问。不可能从中间开始访问。
2. 为了确定其参数地址,可变参数列表中至少有一个命名参数。如果连一个命名参数都没有,就无法使用va_start。
3. 这些宏是无法直接判断实际存在参数的数量和每个参数的是类型。
4. 如果在va_arg中指定了错误的类型,那么其后果是不可预测的。
5. 如果参数列表中有多个命名参数,则在使用va_start时,取的应该是最后边的命名参数的地址。

以上即为个人对可变参数列表的解析,如有错误,敬请指正!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值