简单剖析可变参数列表

在C语言中,通过将函设置为可变参数的形式,可使函数接收多个参数,下面从源码层面对该问题进行简单剖析。

先来看一段代码:

#include<stdio.h>
#include<stdarg.h>
int ave(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);
	}
	va_end(arg);
	return sum / n;
}
int main()
{
	int ret = ave(4, 1, 3, 5, 7);
	printf("ret = %d", ret);
	getch();
}

在这段代码中,有一些关键点,例如:va_list arg;,va_start(arg, n);等,下面找到它的源码,进行简单剖析。

首先,对va_list转到定义,发现下图示:

这里写图片描述
char*#typedef重命名类型为va_list,所以这里可以认为va_listchar*类型,即arg是一个char*类型的变量。原来的代码就可以变成char* arg;

接着,对va_start(arg, n);三次转入定义,发现下图示:

这里写图片描述
此时,可以对照源码类比写好的代码,得到(arg = (char*)&(n)+4);,直接将(arg, n)代入(ap, v)char*代入va_list替换,可以很容易地得到:
((void)(arg = (char*)_ADDRESSOF(n) + _INTSIZEOF(n))
再对_ADDRESSOF(n)转到定义,发现它的源码为(&(v)),即对变量做&操作,而_INTSIZEOF(n))是对变量测字节的操作,在这段代码中,可以简单地认为完成该操作后它的值为4,原来的这句代码就可以变成:
(arg = (char*)&(n)+4)
这句代码的意义在于:初始化arg为未知参数列表的第一个元素的地址,而之后的+4代表指针指向的位置跳过4个字节,指向下一个元素。

然后,再对va_arg(arg,int)两次转入定义,发现:

这里写图片描述
有了上面对va_start(arg, n);的分析以后,就能轻易对va_arg(arg,int);进行类比分析,容易得到 (*(int*)((arg += 4) - 4)); 这句代码需要重点分析
在前面的分析中,了解到arg实际上是一个char*的指针,对指针+4指向它的下一个参数,而这时再对指针-4,再对它强制类型转换为(int*)后解引用,取到的依然是一开始指向的地址,但指针已经指向了下一个地址,为下次操作做准备。

这句代码实现了两个功能:留下了原来的地址,并使指针指向下一个地址。
这里写图片描述

最后,对va_end(arg);转到定义并替换后,得到(arg = (char*)0);,表明在结束程序后,将arg置为空指针,保证安全性。

至此,对可变参数列表的简单剖析就告一段落了.

注,可变参数存在一定的限制:
1.可变参数必须从头开始逐个访问。允许在访问了几个可变参数后终止,但不允许从一开始就访问参数裂变中间的参数;
2.参数列表中至少有一个命名参数,否则无法使用va_list
3.在转到定义后,发现它们全都是宏,这些宏不能直接判断实际存在的参数的数量,也不能判断参数的类型;
4.如果在va_arg中指定了错误的类型,后果不堪设想。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值