C语言可变参数列表解析

我们有时候可能会想,我们一般写的函数都是有固定的参数类型和参数数目的,但是有的函数传递的参数数目不固定,而且类型也可以有多种,就比如我们经常用到的printf()函数,我们在centos下看一下printf的原型:

C语言可变参数列表解析_第1张图片

可以看到printf()函数的第一个参数是const char *类型的字符串,第二个参数是 "...",三个点,这表明第一个参数是必须要传的,第二个以及后面的参数类型和参数个数不确定,具体根据自己实际传递的参数类型和参数个数为准。它的参数列表是可变的,这也就引出了可变参数列表,C语言中的可变参数列表是一个比较有意思的实现,通过将函数实现为可变参数的形式,可以使得函数可以接受1个以上的任意多个参数(不固定)。

可变参数列表具体实现实际是宏的使用,它的实现原理实质上是栈帧结构的运用,我们知道函数在实参实例化的时候,从右向左依次入栈保存,那么我们可以定义一个指针变量,指向变量确定部分的最右边一个,然后通过指针+sizeof(类型)的操作就可以依次访问各个变量,画一张图直观的看一下:

C语言可变参数列表解析_第2张图片



我们先看一个例子:

实现一个函数可以求任意个参数的平均值。

#include 
#include 
#include 


int avg(int n, ...)
{
	//声明一个va_list类型的变量,用于访问参数列表的未确定部分
	va_list arg;
	int i = 0;
	int sum = 0;
	//初始化arg变量,它的第一个参数是va_list 的变量名,第2个参数是省略号前最后一个有名字的参数
	//初始化过程把arg变量设置为指向可变参数部分的第一个参数
	//在avg函数中,可变部分的第一个参数就是参数n的上第一个(往高地址方向)
	va_start(arg, n);
	for (; i < n; i++)
	{
		//va_arg这个宏用于访问参数,这个宏接收两个参数:va_list变量和参数列表下一个参数的类型,
		//在这个例子中,所有的可变参数都是整型。va_arg返回这个参数的值,并使va_arg指向下一个可变参数
		sum += va_arg(arg, int);
	}
	return sum / n;
	//最后,当访问完最后一个可变参数之后,我们需要调用va_end,也就是把arg指针赋为空指针
	va_end(arg);
}


int main(void)
{
	int a = 1;
	int b = 2;
	int c = 3;
	int d = 4;
	int avg1 = avg(4, a, b, c, d);
	int avg2 = avg(2, b, d);
	printf("avg1 = %d\n", avg1);
	printf("avg2 = %d\n", avg2);
	system("pause");
	return 0;
}

那么结果就是:


!注意

可变参数有几个限制条件:

1.可变参数必须从头到尾逐个访问。如果你在访问了几个可变参数之后想半途终止,这是可以的,但是如果你想一开始就访问参数列表中间的参数,那是不行的。

2.参数列表中至少有一个命名参数,如果连一个命名参数都没有,就无法使用va_start,很好理解,如果一个命名参数都没有,那我们就没有一个确定的指向,从而根据这个指向来访问其他不确定部分了

3.这些宏是无法直接判断实际存在参数的数量的,所以需要我们传入。那么问题来了,printf函数,我们也没有传参数个数啊,它怎么确定我们穿了多少个参数和每个参数的类型的。原来,我们在使用printf这个函数时第一个参数中,我们通常会写好多这样的符号%d,%c,%f等等,而后面的参数和前面的%d,%c等都是一一对应的,那么我们只需要知道,第一个字符串参数中有多少个%就能确定有多少个参数,那么类型怎么确定呢,我们知道%d要以整型形式输出,而%c要以字符形式输出,那么通过判断这个我们就可以知道后面每个参数的类型都是什么类型了

4.这些宏无法判断每个参数的类型

5.如果在va_arg中指定了错误的类型,那么其后果是不可预测的。这个很好理解,因为类型决定占内存空间的大小,如果类型都是错误的,那么必然在取得内存中的数据的时候会导致意想不到的错误。

接下来我们研究一下,vc++6.0下这个宏函数的源码:

我们刚才说了,可变参数列表是通过宏实现的,这个宏在stdarg.h这个头文件中,那么我们可以来看一下这个头文件中的宏,在VC++6.0的默认安装目录:

C:\Program Files (x86)\Microsoft Visual Studio\VC98\INCLUDE,找到stdarg.h,打开,搜索va_start

C语言可变参数列表解析_第3张图片 

_INTSIZEOF(n)是为了内存对齐,提高CPU读取内存的效率,这里我们不深入研究,当n为char类型、short类型时,_INTSIZEOF(char) = 4, _INTSIZEOF(short) = 4.

附上一张图说明一下:

C语言可变参数列表解析_第4张图片

over!

你可能感兴趣的:(C语言可变参数列表解析)