本文分析 printf 函数参数压栈顺序的问题,先来个入门第一题,不看答案先做题,看看你会不会怀疑自己的答案。

 

题目:

#include

int main(){
 
 int a=1; 
 printf("%d, %d, %d\n", a, ++a, a++);
 
 return 0;
}

 

结果:

 

 

分析:

1、知识点:a++表示先用 a 后把 a+1,++a 表示先把 a+1,然后再用

 

2、printf()语句多个参数的执行顺序,从右往左

 

3、所以,从右往左:a++,先 a=1,然后 a+1,这时 a 变成 2,++a,a 先加 1,a=3,打印出来:3,3,1

 

编译的时候从右向左,输出的时候从左往右。

 

底层原理:

主要是因为栈(压栈),栈是先进后出,后进先出。

 

C 函数的参数压栈顺序是从右到左,printf 和 scanf 函数都是,采用压栈从右到左的原因如下:

 

printf 函数的原型是:

 

printf(const char* format,…)

 

它是一个不定参函数,我们在实际使用中是怎么样知道它的参数个数呢?这就要靠 format 了,编译器通过 format 中的%占位符的个数来确定参数的个数。

 

现在我们假设参数的压栈顺序是从左到右的,这时,函数调用的时候,format 最先进栈,之后是各个参数进栈,最后 pc 进栈,此时,由于 format 先进栈了,上面压着未知个数的参数,想要知道参数的个数,必须找到 format,而要找到 format,必须要知道参数的个数,这样就陷入了一个无法求解的死循环了。

 

而如果把参数从右到左压栈,函数调用时,先把若干个参数都压入栈中,再压 format,最后压 pc,这样一来,栈顶指针加 2 便找到了 format,通过 format 中的%占位符,取得后面参数的个数,从而正确取得所有参数。

 

所以,如果不存在这种不定参的函数,则参数的压栈顺序无论是从左到右还是从右到左都是没关系的。

 

函数有多个参数时计算总得有个顺序吧?不是从左至右,就是从右至左,抑或从中间向两边;一句话选定一个顺序后就“大家都这么办”,总不能有些函数从左至右,有些函数从右至左,那编译器就太难做了。当初选择从右至左肯定是这样有好多方便之处,比如 printf 中的参数表,由于 C 是基于栈操作的,栈又是后进先出的,从右至左计算压栈,然后按弹出顺序输出到屏幕上刚好顺应了大多数文本从左至右的习惯,很是方便;若从左至右计算压栈,处理就没有这么方便了。

 

知道了这个原理,我们再出两题加强练习:

#include

int main(){
 
 int a=0, b=0;
 printf("%d,%d,%d\n",a++,++b,a);
 a=0; 
 printf("%d %d %d\n",a,a++,a);
 b=0;
 printf("%d %d %d %d %d \n",b, b++, ++b, b++, ++b);

 return 0;
}

 

 

#include

int main(){
 
 int x;
 x=1;   printf("%d %d\n",x,x++);
 x=1;   printf("%d %d\n",x++,x);
 x=1;   printf("%d %d %d\n",x,x++,x);
 x=1;   printf("%d %d %d %d\n",x,++x,x++,x);
 
 return 0;
}

 

 

注意:不同编译器可能出现不同的结果。

 

我们研究此题目只是为了深究一下 C 语言函数参数调用的原理,现实中最好不要这么写代码,这么写你可能会被打死。