深入理解 printf 函数中的参数处理顺序

在阅读文章前,请先思考下面的代码执行结果是什么:

1
2
3
4
5
6
7
8
#include <stdio.h>
int main(){
struct{
int x; int y;
}s[2] = {{2, 0}, {1, 3}}, *p = s;
printf("%d, %d", ++p->x, (++p)->x);
return 0;
}

如果你的答案为 3, 1 ,那你就大错特错了。

实际上,这道题的答案为 2, 1 ,那么这是为什么呢?

printf函数在处理参数的时候是从右向左处理的,其参数从右向左依次压入栈中,存放在栈中从高到低的地址里面,然后再格式化输出,输出时从低地址到高地址输出。即整个操作可以看做两部分:数据的处理(压栈)和格式化的输出(出栈)。

因此函数会先处理 (++p)->x ,因此第二个 %d 对应着 1 。然后再处理 ++p->x ,根据运算符优先级( -> 优先于 ++ ,因此 ++p->x 相当于 ++(p->x) )知第一个 %d 对应着 2


后来我发现事情又没有那么简单,请看下面的代码:

1
2
3
4
5
6
7
#include <stdio.h>

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

按照我们上述理论,总是从右向左解析,那么预期输出应为:

1
3 2 2 1

然而事实上这段代码跑下来实际结果为:

1
3 2 3 3

压栈顺序仍然是从右向左的,只是在底层 i++++i 的实现原理不一样。对于 i++ 的结果,是由ebp寻址函数栈空间来记录中间结果的,在最后给printf压栈的时候,再从栈中把中间结果取出来。而对于 ++i 的结果,则直接压寄存器变量,寄存器经过了所有的自增操作。 —— Go to Dessembly

这段代码的实际处理操作为:

  1. i 的地址入栈;
  2. i=i+1 ,压 i 的地址入栈;
  3. i 的值入栈, i=i+1
  4. i 的地址入栈;
  5. 出栈,为 i 的地址,解析为 3
  6. 出栈,为 2
  7. 出栈,为 i 的地址,解析为 3
  8. 出栈,为 i 的地址,解析为 3

因此结果为 3 2 3 3


参考文献

张春玲.可变参数函数printf调用过程的分析[J].电子制作,2014,000(002):058-058

注:文中所指的printf函数位于C语言标准库stdio.h中。结果均使用MinGW编译器验证,不同编译器结果可能不同。

深入理解 printf 函数中的参数处理顺序

https://mmdjiji.com/2021/01/1201/

作者

吉吉

发布于

2021-01-12

更新于

2024-04-25

许可协议