C语言的变参
函数的变参实际上就是:C语言利用调用栈而提供的一种参数传递的机制。
一、调用栈
我们知道C语言的调用约定为__cdecl,它的特点为:所有参数从右到左依次入栈,这些参数由调用者清除,称为手动清栈。要了解它的确切含义,就先看一下函数调用是发生了什么,假设有如下的程序:
1: #include
2: #include
3:
4: void foo(int count, ...)
5: {
6: va_list args;
7: int t = 0;
8: int i = 0;
9:
10: va_start(args, i);
11:
12: for (i = 0; i < count; ++i) {
13: t = va_arg(args, int);
14: printf("%d", t);
15: }
16:
17: va_end(args);
18: }
19:
20: int main(int argc, char * argv[])
21: {
22: foo(5, 1, 2, 3, 4, 5);
23: return 0;
24: }
25:
在这段代码中,定义了一个带变参的函数foo,第一个参数为变参的个数,然后在main函数中对其进行了调用。当我们在调用foo函数的时候,按照调用约定,栈如下图所示:
我们知道,栈是向低地址方向生长的,当调用foo时,先由右向左将参数如栈,最后将返回地址如栈,这个结构只有调用者知道,这是调用约定规定的。
二、被调用函数内部
进入foo函数以后,foo函数内部是对上图栈的结构一无所知,准确的来说是变参的部分一无所知。它只知道它被调用了,有一个参数为变参的个数。为了获取这些变参,我们可以通过宏va_start来获取参数列表,这个宏有一个参数指定了第一个变参在那个参数后面。通过上图我们可以看出,要获取第一个变参的地址必之它的地址在count参数的后面。
获取每一个参数是通过宏va_arg来进行的,它需要指定每个参数的类型。从这一点我们可以看出,foo函数真的对栈上的变参情况一无所知。这一点告诉我们,foo函数内部要想主动知道变参的个数,是不可能的。当然,你可以通过其它方式获知。而且,刚好要获取上面的每个参数的首地址,只需要进行这样的计算即可:第一个变参的地址 + sizeof(参数类型)。我都怀疑,参数的入栈顺序是因为方便取才从右边的参数先如栈的。
最后一个和变参相关的宏是va_end,它实际上是简单的将变参表的指针置为空。
三、总结
有了上面的知识以后,我们可以确切的回答一些和变参相关的问题了。比如,我以前考虑过的:
1. 变参可以直接往下一层函数调用传吗?
显然不行,因为带变参的函数内部对变参一无所知(变参的个数和类型),也就无法直接向下传递了。但是,你可以将变参表的首地址传递下去。
2. 上面提到的获取变参的个数的问题。
3. 可以定义变参函数为bar(…)的形式吗?
显然不行,因为这样的话就无法获取变参表的首地址了。而且,这也是ANSI C 不允许的。
本篇文章来源于:开发学院 http://edu.codepub.com 原文链接:http://edu.codepub.com/2010/1223/28251.php