【C语言】可变参数列表源码解析

主函数在调用函数时,会进行传参,当实参的数量发生变化但被调用函数的功能没有发生改变,这时候被调用函数的参数数量也要发生改变。C语言中有一个比较有意思的实现------可变参数列表

可变参数列表,通过将函数实现为可变参数的形式,可以使得函数可以接受1个以上的任意多个参数(不固定)。

举个例子:

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

#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()
{
	int a = 1;
	int b = 2;
	int c = 3;
	int avg1 = average(2, a, c);
	int avg2 = average(3, a, b, c);
	printf("avg1=%d\n", avg1);
	printf("avg2=%d\n", avg2);
	return 0;
}

结果如下:

源码解析

上面的代码,对于没学过可变参数列表的人来说是一脸懵逼,上面代码中的va_x都是什么意思呢,接下来 ,就逐一了解。
首先要了解的是:
函数在传参的时候,首先在栈区push的是c接下来依次从右向左push参数,最后是要计算的参数的个数。

在调用后面的average函数时把要计算的参数的个数传给了n,后面的参数到底是怎么传过去的目前还不知道。
前面的都了解了,接下来就了解这些陌生的va_x。

va_list

在代码区选中va_list右击选择转到定义,看到下图所示的代码:

可以说va_list就是一个char*

va_start

在代码区选中va_start右击选择转到定义,看到如下图所示代码:

了解定义后,做相应的替换,得到如下结果:
这里写图片描述

va_start(arg, n);这段代码的意思是初始化arg为未知参数列表的第一个参数的地址。

va_arg


了解定义后,做相应的替换,得到如下结果:

va_arg(arg, int);向sum上加数字,直到for循环结束。

va_end

在代码区选中va_end右击选择转到定义,看到如下图所示代码:

了解定义后,做相应的替换,得到如下结果:

代码替换

把不了解的va_x替换成所认识的东西。

#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);
	//__crt_va_start_a(ap, v) ((void)(ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v)))
	(arg = (char*)&(n)+4);//初始化arg为未知参数列表的第一个参数的地址
	for (i = 0; i < n; i++)
	{
		//sum += va_arg(arg, int);
		//__crt_va_arg(ap, t)     (*(t*)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))
	    sum +=(*(int*)((arg += 4) - 4));
		//此时arg指向的是c的地址,但表达式留下的是a的地址
		//为什么指向c的地址?下一个处理的是c
	}
	return sum / n;
	//va_end(arg);
	//__crt_va_end(ap)        ((void)(ap = (va_list)0))
	arg = (char *)0;//arg赋值成空指针
}
int main()
{
	int a = 1;
	int b = 2;
	int c = 3;
	int avg1 = average(2, a, c);
	int avg2 = average(3, a, b, c);
	printf("avg1=%d\n", avg1);
	printf("avg2=%d\n", avg2);
	return 0;
}

运行结果:

  • 声明一个va_list类型的变量arg,它用于访问参数列表的未确定部分。
  • 这个量是调用va_start来初始化的。他的第一个参数是va_list的变量名,第二个参数是省略号前最后一个有名字的参数。初始化过程把arg变量设置为指向可变参数部分的第一个参数。
  • 为了访问参数,需要使用va_arg,这个宏接受两个参数:va_list变量和参数列表中下一个参数的类型。在这个例子中所有可变参数都是整型。va_arg返回这个参数的值,并使用va_arg指向下一个可变参数。
  • 最后,当访问完毕,最后一个可变参数之后,需要调用va_end

可变参数的限制

注意:

  • 可变参数必须从头到尾逐个访问。如果在访问了几个可变参数之后想半途中止,这是可以的,但是,如果想一开始就访问参数列表中间的参数,那是不行的。
  • 参数列表中至少有一个命名参数。如果连一个命名参数都没有,就无法使用va_start
  • 这些宏是无法直接判断实际存在参数的数量。
  • 如果va_arg中指定了错误的类型,那么其后果是不可预测的。

相关例题

1.使用可变参数,实现函数,求函数参数的最大值。

#include <stdio.h>
#include <stdarg.h>
int Max(int n, ...)
{
	int max = 0;
	int i = 0;	
	va_list arg;
	va_start(arg, n);
	max = va_arg(arg, int);
	for (i = 1;i < n; i++)
	{
		int tmp = va_arg(arg,int );
		if (tmp > max)
		{
			max = tmp;
		}
	}
	va_end(arg);
	return max;
}
int main()
{
	int max1 = Max(2, 1, 4);
	int max2 = Max(3, 3, -1, -2);
	printf("%d\n", max1);
	printf("%d\n", max2);
	return 0;
}

2.模拟实现printf函数,可完成下面的功能
//能完成下面函数的调用。
//函数原型:
//print(char *format, …)
//

void show(int n)
{
	if (n>9)
	{
		show(n / 10);
	}
	putchar(n % 10 + '0');
}
void print(const char *format, ...)
{
	va_list arg;
	va_start(arg, format);
	while (*format)
	{
		switch (*format)
		{
		case 's':
		{
			char *ret = va_arg(arg, char*);
			while (*ret)
			{
				putchar(*ret);
				ret++;
			}
		}
		break;
		case 'd':
		{
			int ret = va_arg(arg, int);
			show(ret);
		}
		break;
		case 'c':
		{
			int ch = va_arg(arg, char);
			putchar(ch);
		}
		break;
		default:
			putchar(*format);
			break;
		}
		format++;
	}
}

int main()
{
	print("d s c\n", 100, "hehe", 'w');
	return 0;
}

总结

可变参数列表的第一个参数是不一定要传的,可变参数列表,在调用函数时,简化了传参的数量,使函数的适应能力更强。可变参数的实现过程就是使用宏的封装。只要完成替换,就可以自行分析了。

如有不足之处,欢迎指正!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值