C语言:可变参数列表

在这里插入图片描述


一、什么是可变参数列表

可变参数列表听着陌生,但其实我们经常在printf和scanf函数中使用。
如下:
在这里插入图片描述
在这里插入图片描述
其中的… 就是可变参数列表在形参中的表示。

二、可变参数列表的使用

对于可变参数列表的使用关乎到四个宏的使用,其分别是va_listva_startva_argva_end

va_list是(char*)重命名的类型,定义可以访问可变参数的变量。
va_start(ap, v) ap是定义的可变参数变量,v是形参中可变参数前第一个参数名,其作用是使ap指向可变参数部分。
va_arg(ap, t) ap是定义的可变参数变量,t是可变参数的类型,根据类型,访问可变参数列表中的数据。
va_end(ap) ap是定义的可变参数变量,使ap变量置空,作为结束使用。

在次我用求解5个数中最大值这一问题,来演示可变参数列表的使用。
代码如下:

//VS2022中使用
int FindMax(int num, ...)
{
	va_list arg;
	__crt_va_start(arg, num);
	int max = __crt_va_arg(arg, int);

	for (int i = 0; i < num-1; i++)
	{
		int cur = __crt_va_arg(arg, int);
		if (max < cur)
		{
			max = cur;
		}
	}
	__crt_va_end(arg);

	return max;
}

int main()
{
	int max = FindMax(5, 10, 20, 30, 40, 50);
	
	printf("%d\n", max);
	return 0;
}

三、理解可变参数列表

其宏的实现如下:
在这里插入图片描述
对于va_list和__crt_va_end而言非常好理解。va_list就是(char*)的重命名,__crt_va_end就是将ap可变参数变量置为0,而对于__crt_va_start_a和__crt_va_arg就相对难理解。

1.汇编角度理解__crt_va_start_a和__crt_va_arg的作用

我们都知道,函数形参的实例化,是在栈顶直接进行压栈操作,是从右向左进行的。
如下:
粗线框部分是将要压栈的部分,32h,28h,1Eh…分别是50,40,30的十六进制
在这里插入图片描述

在这里插入图片描述
示意图如下:

在这里插入图片描述
也就是说,函数的形参的临时拷贝的相对位置关系是固定的。那么如图,当我知道num的地址,那么我就可以访问可变参数列表中的数据。

  • __crt_va_start_a

0x0113fc10就是esp当前位置,0x0113fc08就是ebp-8的位置,也就是变量arg所在空间。
0x0113fc1c就是ebp+0ch的位置,也就是可变参数列表数据中第一个元素(10)的位置。

此时__crt_va_start(arg, num)未执行。
在这里插入图片描述

此时__crt_va_start(arg, num)执行完毕。
0x0113fc08(arg变量)此时存储的地址就是可变参数列表中第一个元素的地址。

在这里插入图片描述
示意图:
在这里插入图片描述
在这里插入图片描述
如图,__crt_va_start_a等价于(char*)(&(num))+_INTSIZEOF(int),就是通过num的地址,加上_INTSIZEOF(v)找到可变参数列表第一个元素(10)的地址,存放到变量arg中。

  • __crt_va_arg

此时__crt_va_arg未执行。
arg中存放的是可变参数列表中,第一个元素(10)的地址。

在这里插入图片描述
此时__crt_va_arg执行完毕。
arg中地址所指向的空间,为可变参数列表第二个元素(20)的地址,并将可变参数列表中第一个元素的内容放在max变量中。
那么是如何进行的?
先将arg中的地址放在eax中,再让eax自增4,再放回arg中,此时arg中地址是可变参数列表第二个元素(20)的地址。
再读取arg中的地址放到ecx中,再寻找ecx-4处的地址,也就是可变参数列表第一个元素的地址,将其放到edx中,再将edx中地址所指向空间的内容放到ebp-14h处,也就是将可变参数列表第一个元素(10)放到max的空间中。
在这里插入图片描述

而上图中00D117BB,00D117BE,00D117C1指令就完成了(ap+=_INTSIZEOF(t))【先即为表达式X】的操作,00D117C4,00DC117C7完成的就是X-_INTSIZEOF(t)的操作。
在这里插入图片描述
示意图:

在这里插入图片描述
到此,对于**__crt_va_start_a** 和 __crt_va_arg就基本理解,那么什么事_INTSIZEOF(n)?

2._INTSIZEOF(n)的理解

_INTSIZEOF(n)其实就是求4的对齐数。那么为什么要怎么做,直接加上可变参数类型的大小不好吗?

  • 为什么?
    我们先看如下代码:
int FindMax(int num, ...)
{
	va_list arg;
	__crt_va_start(arg, num);
	int max = __crt_va_arg(arg, int);

	for (int i = 0; i < num - 1; i++)
	{
		int cur = __crt_va_arg(arg, int);
		if (max < cur)
		{
			max = cur;
		}
	}
	__crt_va_end(arg);

	return max;
}

int main()
{
	//int max = FindMax(5, 10, 20, 30, 40, 50);

	//对于短整型而言,在形参实例化中,会发生整形提升
	char e = 'e';
	char d = 'd';
	char c = 'c';
	char b = 'b';
	char a = 'a';

	int max = FindMax(5, a, b, c, d, e);


	printf("%d\n", max);
	return 0;
}

注意在上述代码中,我传递的可变参数列表是char类型,而我在使用__crt_va_arg时,使用的是int类型。那么我是否可以得到正确答案?
如下:
在这里插入图片描述
101所对应的ASCII值就是e。

其实,在我们传递短整型时,形参会发生整形提升。
如下:
在这里插入图片描述
在这里插入图片描述

其实movsx就是会将短整型形参,发生整形提升。而对于这种情况,我们要是在__crt_va_arg中加减类型的大小就会造成错误,所以才要_INTSIZEOF(n)。

  • 如何实现
    首先4的对齐数,可以理解为4的最小整数倍,那么如何求解?
    如图:
    在这里插入图片描述

总结

以上就是,我对于可变参数列表的一些理解。感谢观看!!!
在这里插入图片描述

  • 13
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

水月梦镜花

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值