printf函数进行a++和++a等操作的实现原理。
最近在看函数调用的时候突发奇想了几个用例,想证明一下结果,具体的例子如下:
int a = 0;
int b = 1;
printf("tinus : %d %d\n", a, b);
printf("tinus : %d %d\n", a++, b++);
printf("tinus : %d %d\n", a++, a++);
printf("tinus : %d %d\n", ++a, a++);
printf("tinus : %d %d\n", a++, ++a);
printf("tinus : %d %d\n", ++a, ++a);
你知道上面几个printf最后的打印是多少吗?
这里面需要了解函数调用约定, 还有在调用函数的时候实参部分如果是需要计算的时候函数是如何处理的。在之前我已经有博客说明了函数调用约定的方式,不熟悉的可以去看下我的博客:函数调用约定。
通过函数调用约定可以知道printf参数是右向左先进行入栈操作。但是上面printf函数中参数有运行操作,这可怎么办了?到底编译器是怎么处理它们的了?,这里我们从汇编的角度来看一下:
printf("tinus : %d %d\n", a, b)
由上面的汇编可以知道,函数先把b的值进行入栈,然后入栈a的值,最后会将字符串入栈, 然后去函数实现的地方运行函数。
printf("tinus : %d %d\n", a++, b++)
由上面汇编可知,
1.b++因为是后加,所以编译器先将b的值存储到一个临时地址tmp1(别名)处,
2.然后将b取出进行自身加一。
3.将运算后的值放回b。
4.函数执行a++,重复上面1--3的步骤,其中编译器先将a的值存储到一个临时地址tmp2(别名)处。
5.最后运算完后,编译器从临时地址tmp1处取出值进行入栈操作。
6.编译器从临时地址tmp2处取出值进行入栈操作。
7..编译器将字符串进行入栈操作。
2.然后进行b自身加一,同样的a++也是如此,最后运算完后,编译器取出开始存放在临时地址的值进行从右向左入栈,最后跳转函数实现地址调用函数。
printf("tinus : %d %d\n", a++, a++)
由上面汇编可知,
1.函数最后一个参数a++,编译器先将a的值存储到一个临时地址tmp1(别名)处。
2.然后进行a取出进行自身加一。
3.将运算后的值放回a。
4.函数执行倒数第二个a++,重复上面1--3的步骤,其中编译器先将a的值存储到一个临时地址tmp2(别名)处。
5.最后运算完后,编译器从临时地址tmp1处取出值进行入栈操作。
6.编译器从临时地址tmp2处取出值进行入栈操作。
7..编译器将字符串进行入栈操作。
8.编译器最后跳转函数实现地址调用函数。
printf("tinus : %d %d\n", ++a, a++)
由上面汇编可知,
1.函数最后一个参数a++,编译器先将a的值存储到一个临时地址tmp1(别名)处。
2.然后将a取出进行自身加一。
3.将运算后的值放回a。
4.函数执行倒数第二个++a。
5.将a取出进行自身加一。
6.将运算后的值放回a。
7.运算完后,编译器从临时地址tmp1处取出值进行入栈操作。
8.编译器从a处取出值进行入栈操作。
9..编译器将字符串进行入栈操作。
10.编译器最后跳转函数实现地址调用函数。
printf("tinus : %d %d\n", a++, ++a)
由上面汇编可知,
1.执行函数最后一个参数++a,
2.将a值取出进行自身加一。
3.将运算后的值放回a。
4.函数执行倒数第二个a++。
5.编译器先将a的值存储到一个临时地址tmp1(别名)处。
6.将a取出进行自身加一。
7.将运算后的值放回a。
8.运算完后,编译器从a处取出值进行入栈操作。
9.编译器从临时地址tmp1处取出值进行入栈操作。
10..编译器将字符串进行入栈操作。
10.编译器最后跳转函数实现地址调用函数。
printf("tinus : %d %d\n", ++a, ++a)
由上面汇编可知,
1.执行函数最后一个参数++a,
2.将a值取出进行自身加一。
3.将运算后的值放回a。
4.函数执行倒数第二个++a。
5.编译器将a取出进行自身加一。
6.编译器将运算后的值放回a。
7.运算完后,编译器从a处取出值进行入栈操作。
9.编译器继续从a处取出值进行入栈操作。
10..编译器将字符串进行入栈操作。
11.编译器最后跳转函数实现地址调用函数。
总结
通过上面你应该知道函数参数如果有运算的时候,它的执行流程了吧,这里有让我了解到了,a++和++a的不同,a++会产生一个临时地址存放,然后返回这个地址的值给调用者,然后才进行a的加一操作并赋给a自身。 而++a先进行加一后,将加一的值返回给调用者。