这一直是一个迷惑人的问题,网上找竟然找不到答案,所以这是本文的目的。
C或C++的编译器被设计成按照从右向左的顺序来处理函数调用中的变元。但是编译器扫描变元的顺序
却是从左向右的。使用从左向右的处理顺序,编译器只需要一边扫描变元一边生成代码即可,但从右向左的顺
序却要编译器记住扫描过的所有变元。所以问题就来了,为什么C++设计者不设计成从左向右的函数参数处理
顺序呢?难道这是他们工作的疏忽吗,不是的。
原因是为了处理那些变元不固定的函数的调用。用个例子来说明,看下面的程序:
我们来看当main调用add时内存中的堆栈是怎么样的:#include<iostream> using namespace std; int add(int count,...)//...指有不确定的变元个数 { int sum=0; int *p=&count+1;//p指向比count高一个单元的地址 for(int i=0;i<count;i++) { sum+=*p; p++; } return sum; } int main() { count<<add(3,1,2,3)<<endl;//sum=6 return 0; }
编译器按照从右向左的顺序把所有的参数压入栈中,当执行到p=&count+1的时候,使p指向比count高一个
字节的地址,然后for循环使接下的的三个数相加得到和sum。编译器之所以能够工作是因为编译器知道count的
地址,因为它比函数的返回地址高一个字节(栈的地址从上向下是逐渐增加的),所以可以直接引用它。
现在假设编译器不是按照从右向左的顺序把参数压入栈的,而是按照从左向右的顺序,堆栈的情况如下:
现在如图所示,编译器将无法找到count的地址。因为编译器知道返回地址的地址,但不知道参数的个数
(因为不知道count等于多少),所以无法找到count的地址,也就不知道count等于多少。而要知道参数的个数就
需要知道count等于多少,所以编译器将陷入糊涂当中。这就是为什么C或C++要从右向左的顺序处理变元的原因了。
再来说一下变元的宏,在C或C++中,有些函数的变元计数是由第一个变元间接指定的。如下面的函数调用:
printf(“x = %d y = %d\n”,x,y);
printf的第一个变元是格式字符串,它必须包含调用中每个附加变元的变换码(以%开始的格式符)。上面有
两个变换码,表明在它的后面有两个要输出的数x和y。为了使访问有变长变元的参数列表更容易,C++提供了
va_list,va_start,va_arg,va_end这样几个宏。各个宏的功能如下:
va_list(p)=void *p;
va_start(p,count)=p=&count+1;
va_arg(p,int)=*((int *)p)++;
va_end(p)用于整理;
用宏把上面的程序改如下:
#include<iostream> #include<cstdarg> using namespace std; int add(int count,...) { int sum=0; va_list(p); va_start(p,count); for(int i=0;i<count;i++) { sum+=va_arg(p,int); va_end(p); } return sum; } int main() { cout<<add(3,1,2,3)<<endl; return 0; }