可变参数列表
我们想通过一个例子来引出我们这个话题.如果我们想要求两个数的最大值,这个函数是非常容易的.
int GetMax(int x, int y)
{
if (x > y)
{
return x;
}
return y;
}
int main()
{
int a = 10;
int b = 20;
int max = GetMax(a, b);
printf("max = %d\n", max);
return 0;
}
但是突然有一天你想求10个、20个......数中的最大值,请问我们应该如何做,此时我们想很简单,我们是可以使用一个数组,可是有一天我们不想使用数组,就是想通过传入参数额形式来让我们完成,这就需要可变参数了.我们先来看一下,不用担心,后面都会和大家分析到.
int GetMax(int num,...)
{
}
先来说一下定义.在计算机程序设计,一个可变参数函数是指一个函数拥有不定引数,即是它接受一个可变数目的参数。不同的编程语言对可变参数函数的支持有很大差异。 一般而言,在设计函数时会遇到许多数学和逻辑操作,是需要一些可变功能。例如,计算数字串的总和、字符串的联接或其他操作过程,都可以存在任意数量的参数。
我们是不是在之前使用过可变参数函数,是的,我们确实使用过,例如我们上文的格式化输入输出.
int main()
{
int a = 10;
int b = 20;
printf("%d\n", a); //可以打印一个
printf("%d %d\n", a,b); //也可以打印两个
return 0;
}
可变参数原理
先来说一下可变参数的原理,我们知道当我们在函数调用进行传入参数的时候,请问我们参数是否会形成临时拷贝?一定会的,不仅仅会形成临时拷贝,而且是从右向左一个一个拷贝的,甚至VS系列中参数空间位置是紧邻的.
int main()
{
int max = FindMax(5, 1, 2, 3, 9, 7);
printf("max = %d\n", max);
system("pause");
return 0;
}
他的栈帧地址我们可以这样的画.
这里我们开始解释我们可变参数的原理,我们也是可以通过栈帧来找到了每一个参数,试想一下,如果我们拿到了一个参数的地址,按照某种特定的规则,我们就可以得到所有的参数.
可变参数使用
根据上面的说法,我们可以自己实现一个寻找参数的机制,不过C语言已经早就帮助我们实现好了,这里我们看一下.
va_list
va_start
va_arg
va_end
首先这里面是四个宏,下面我们先说一下他们的用法,然后解释一下功能,最后基本的刨析原理.多的不说,我们先来看一下结果,后面和大家进行分析.
#include <stdarg.h>
int FindMax(int num, ...)
{
va_list arg;
va_start(arg, num);
int max = va_arg(arg, int);
for (int i = 0; i < num - 1; i++)
{
int x = va_arg(arg, int);
if (max < x)
{
max = x;
}
}
va_end(arg);
return max;
}
int main()
{
int max = FindMax(5, 1, 2, 3, 9, 7);
printf("max = %d\n", max);
system("pause");
return 0;
}
va_list : 这是一个类型, 被重命名过 typedef char* va_list;
va_start: 这个暂停说下
va_arg : 这个也暂停说
va_end : 这个是只为空指针
#define va_end __crt_va_end
#define __crt_va_end(ap) ((void)(ap = (va_list)0))
上面我们说了,既然我们是函数,那么函数的参数一定会被压入栈中,那么对于可变参数,由于我们的参数的个数是不确定的,所以我们必须手动传入的参数作为我们标识我们的参数有多少个,这就是我们第一个参数的值,所以我们绝对有能力知道所有的参数.
va_list就是一个类型,本质就是char* 的指针,至于为何是char类型的指针,这是因为他加上1只会跳过一个字节.我们想要这个指针指向我们的实际的参数位置,这里就是我们的va_start作用了.看一下他的定义.
#define va_start _crt_va_start
#define _crt_va_start(ap,v) ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )
这里来解释一下_ADDRESSOF,这个就是给知道我们的世家的参数的位置提供的供能,具体等一下我们的谈.
这个我们暂停一下,先说结论,他的作用就是计算4的的数,如果可以被4整除,可以的,但是如果不能,那么就计算最小的可以被数.例如7不可以被4整除,那么计算结果是8就可以了.
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1)
这里我们就可以解决下面的问题.
#include <stdarg.h>
int FindMax(int num, ...)
{
va_list arg;
va_start(arg, num);
int max = va_arg(arg, int);
for (int i = 0; i < num - 1; i++)
{
int x = va_arg(arg, int);
if (max < x)
{
max = x;
}
}
va_end(arg);
return max;
}
int main()
{
int max = FindMax(5, 'a', 'b', 'c', 'd', 'e');
printf("max = %d\n", max);
system("pause");
return 0;
}
我们知道,寻找我们参数的时候,我们需要知道两个最关键在的元素.
- 参数地址
- 参数的大小
其中参数地址我们已经使用宏可以很容易的找到,我们也可以接受每一个参数的大小编译器也可以帮助我们做好,但是这里存在一个问题,我们的传入的是char,在计算的时候确实int,这里给我们很大的疑惑.其实我们这里只需要看一下我们的的汇编代码我们就可以明白了.在VS2013开发环境下看一下.
看一下他的这条指令的解释,本质上可以理解为整型提升.
MOV BL,80H
MOVSX AX,BL
运行完以上汇编语句之后,AX的值为FF80H。由于BL为80H=1000 0000,最高位也即符号位为1,在进行带符号扩展时,其扩展的高8位均为1,故赋值AX为1111 1111 1000 0000,即AX=FF80H。
通过查看汇编,我们看到,在可变参数场景下:
- 实际传入的参数如果是char,short,float,编译器在编译的时候,会自动进行提升(通过查看汇编,我们都能看到)
- 函数内部使用的时候,根据类型提取数据,更多的是通过int或者double来进行
va_arg
这里我们还要明白一件事情,我们找到了实际的参数的时候,指针需要一步步的遍历的我们的所有的参数,也就是这里我们需要知道两个内容
- 拿到一个参数的完成的内容
- 移动指针到下一个参数那里
#define _crt_va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
这里我们可以很容易的发现,在VS2013中,这里我们先移动的指针,让后让指针回指到原来的位置,这样就可以得到一整个的元素的内容,并且指针也被更新了.
计算规则
下面我们学下一下前面我们并没有仔细谈到的两个计算公式.
_ADDRESSOF
这个主要在va_start中使用.
#define va_start _crt_va_start
#define _crt_va_start(ap,v) ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )
他的作用可以理解找到我们可变参数第一个参数也就是num的地址,然后将ap指针移动到我们的实际参数的元素的位置.
_INTSIZEOF
这个宏才是比较的困难的,上面我们说了找4的倍数.
#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)的意思:计算一个最小数字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的最小整数倍取整,那么本质是:x=4*m,m是具体几倍。对7来讲,m就是2,对齐的结果就是8而m具体是多少,取决于n是多少.如果n能整除4,那么m就是n/,如果n不能整除4,那么m就是n/4+1.上面是两种情况,如何合并成为一种写呢?
( n+sizeof(int)-1) )/sizeof(int) -> (n+4-1)/4
如果n能整除4,那么m就是(n+4-1)/4->(n+3)/4, +3的值无意义,会因取整自动消除,等价于 n/4,如果n不能整除4,那么n=最大能整除4部分+r,1<=r<4 那么m就是 (n+4-1)/4->(能整除4部分+r+3)/4,其中4<=r+3<7 -> 能整除4部分/4 + (r+3)/4 -> n/4+1
搞清楚了满足条件最小是几倍问题,那么,计算一个最小数字x,满足 x>=n && x%4==0,就变成了((n+sizeof(int)-1)/sizeof(int))[最小几倍] * sizeof(int)[单位大小] -> ((n+4-1)/4)*4这样就能求出来4字节对齐的数据了,其实上面的写法,在功能上,已经和源代码中的宏等价了.
((n+4-1)/4)* 4,设w=n+4-1, 那么表达式可以变化成为 (w/4)*4,而4就是2^2^,w/4,不就相当于右移两位吗?,再次*4不就相当左移两位吗?先右移两位,在左移两位,最终结果就是,最后2个比特位被清空为0!也就是w & ~3 .所以就变成了(n+4-1) & ~(4-1)那么我们的结果就出来得了 (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) ).
标签:va,函数,int,max,arg,参数,可变,我们 From: https://blog.51cto.com/byte/7663814