可变参数列表
本章重点
学会使用可变参数列表的使用与原理
函数传参补充知识
- 如果函数没有形式参数,仍然可以给函数传递参数。
- 在c语言中,只要发生了函数调用并且传递了函数,必定形成临时变量。
- 所谓的临时拷贝本质就是在栈帧内部形成的,从右向左依次形成临时拷贝(变量)。
求两个数据中的最大值
#include <stdio.h>
//求两个数据中的最大值
int FindMax(int x, int y)
{
if (x > y) {
return x;
}
return y;
}
int main()
{
int x = 0;
int y = 0;
printf("Please Eneter Two Data# ");
scanf("%d %d", &x, &y);
int max = FindMax(x, y);
printf("max = %d\n", max);
return 0;
}
- 如果未来我们的需求变了,不再求固定的数据个数的最大值
- 而是求任意多个数据中的最大值(至少一个),要求不能使用数组
- 因为目前参数个数不确定,那么函数编写的时候,参数个数也无法确定,换句话说,函数也就没法编写
- 不过,C提供了满足该场景的解决方案:可变参数列表
#include <stdio.h>
#include <windows.h>
//num:表示传入参数的个数
//可变参数列表至少有一个参数
int FindMax(int num, ...)//可变参数列表
{
va_list arg; //定义可以访问可变参数部分的变量,其实是一个char*类型
va_start(arg, num); //使arg指向可变参数部分
int max = va_arg(arg, int); //根据类型,获取可变参数列表中的第一个数据
int i = 0;
for (i = 0; i < num - 1; i++) {//获取并比较其他的
int curr = va_arg(arg, int);
if (max < curr) {
max = curr;
}
}
va_end(arg); //arg使用完毕,收尾工作。本质就是将arg指向NULL
return max;
}
int main()
{
int max = FindMax(5, 11, 22, 33, 44, 55);
printf("max = %d\n", max);//55
system("pause");
return 0;
}
使用
va_list
类型的变量声明一个可变参数列表的访问指针,通常命名为arg
。使用
va_start(arg, last_fixed_param)
宏来初始化可变参数列表。last_fixed_param
是最后一个固定参数的名称,也就是num,在这个参数之后是可变数量的参数。这个宏会将arg
指针指向可变参数列表的起始位置。使用
va_arg(arg, type)
宏来获取可变参数列表中的参数值。type
是参数的数据类型。该宏从可变参数列表中获取下一个参数的值,并将arg
指针移动到下一个参数的位置。使用
va_end(arg)
宏来结束对可变参数列表的访问。这个宏执行一些必要的清理工作,并将arg
指针置为NULL
。
如果将参数改成char类型,求char类型变量中的最大值,代码会有问题吗?
#include <stdio.h>
#include <windows.h>
//num:表示传入参数的个数
int FindMax(int num, ...)
{
va_list arg; //定义可以访问可变参数部分的变量,其实是一个char*类型
va_start(arg, num); //使arg指向可变参数部分
int max = va_arg(arg, int); //根据类型,获取可变参数列表中的第一个数据
for (int i = 0; i < num - 1; i++) {//获取并比较其他的
int curr = va_arg(arg, int);//va_arg(arg, char),这是不正确的
if (max < curr) {
max = curr;
}
}
va_end(arg); //arg使用完毕,收尾工作。本质就是讲arg指向NULL
return max;
}
int main()
{
char a = '1'; //ascii值: 49
char b = '2'; //ascii值: 50
char c = '3'; //ascii值: 51
char d = '4'; //ascii值: 52
char e = '5'; //ascii值: 53
int max = FindMax(5, a, b, c, d, e);
printf("max = %d\n", max);
system("pause");
return 0;
}
先来解释一下汇编代码中的movsx是什么意思?
通过查看汇编,我们看到,在可变参数场景下:
1. 实际传入的参数如果是char,short,float,编译器在编译的时候,会自动进行提升(通过movsx指令进行到当前计算机寄存器的位数)
2. 函数内部使用的时候,根据类型提取数据,更多的是通过int或者double来进行。
注意事项
- 可变参数必须从头到尾逐个访问。如果你在访问了几个可变参数之后想半途终止,这是可以的,但是,如果你想一开始就访问参数列表中间的参数,那是不行的。
- 参数列表中至少有一个命名参数。如果连一个命名参数都没有,就无法使用 va_start 。
- 这些宏是无法直接判断实际存在参数的数量。
- 这些宏无法判断每个参数的类型。
- 如果在 va_arg 中指定了错误的类型,那么其后果是不可预测的。
原理
- 可变参数列表对应的函数,最终调用也是函数调用,也要形成栈帧。
- 栈帧形成前,临时变量是要先入栈的,根据之前所学,参数之间位置关系是固定的。
- 通过上面汇编的学习,发现了短整型在可变参数部分,会默认进行整形提升,那么函数内部在提取该数据的时候,就要考虑提升之后的值,如果不加考虑,获取数据可能会报错或者结果不正确。
我们接下来就看看这几个宏的含义:
1、va_list arg;
2、va_start(arg, num);
3、va_end(arg);
现在我们来重点了解一下这个宏#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
前提: 为了后面方便表述,我们假设sizeof(n)的值是n(char 1,short 2, int 4)
我们在32位平台,vs2013下测试,sizeof(int)大小是4,其他情况我们不考虑
根据上面学到的知识,_INTSIZEOF(n)中的参数可以是变量类型或者变量名。
我们来计算一下参数为char和short,该宏的值为多少?
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
结果都是4个字节
_INTSIZEOF(n)的意思:计算一个最小数字x,满足 x>=n && x%4==0,其实就是一种4字节对齐的方式
是什么: 比如n是:1,2,3,4 对n进行向 sizeof(int) 的最小整数倍取整的问题 就是 4
比如n是:5,6,7,8 对n进行向 sizeof(int) 的最小整数倍取整的问题 就是 8
为什么:要有这个4字节对齐:
结合之前栈帧的学习、上面代码的测试结果和movsx指令我们就可以知道
怎么办到的:
本章结束啦,再见啦。