可能不少朋友刚开始学C的时候和我一样都曾想过这些问题,为此我做了一点简单的分析,供有疑问的同学参考。
问题1: v++与++v的区别
首先举两个栗子:
源码:
1 |
|
输出结果: 3,2,1
1 |
|
输出结果: 4,4,4
为什么第一个输出3,2,1而不是1,2,3?而第二个结果都相等?请看下文
反汇编:
通过汇编代码可以很清晰的看到其硬件操作,对解决这个问题有很大帮助。注: 我用的是VC编译系统, 不同的编译器结果可能会不同,下一节有提到。
v++:
- 首先
mov dword ptr[v], 1
给 v 赋初值 1 mov eax, dword ptr[v]
把 v 的值传给寄存器 eaxmov dword ptr[ebp-0D0h], eax
eax 的值再放入 ebp-0D0h 地址对应的4字节空间, 也就是 i 的初值1- 看倒数第8行:
mov eax, dword ptr[ebp-0D0h]
eax 的值变回 ebp-0D0h 所对应的1 - 最后
push eax
第一个把eax压入栈内,最后弹出来,所以这是printf()的倒数第1个参数,也是第3个输出的值:1 - 同理,
mov ecx, dword ptr[v]
add ecx, 1
mov dword ptr[v], ecx
使 v 自增1 - 接下来
mov edx,dword ptr[v]
mov edx, dword ptr[ebp-0D4h]
把v的值2传给ebp-0D4h对应的内存空间 - 倒数第6行
mov ecx, dword ptr[ebp-0D4h]
然后push ecx
把ecx压入栈,printf()得到倒数第2个参数,所以第二个输出的值为 2 - 最后入栈的3第一个出栈
- printf()的后面三个参数分别是3,2,1,所以输出结果也是如此
++v:
- 第1行到第9行的一大堆指令就是让i自增3次变成4
- 这时i的值为4,分别mov传给eax,ecx,edx最后push,所以printf()得到的3个参数都为4。这是输出
4, 4, 4
的原因
得到结论: 函数的三个参数v++,v++,v++是分别压入v+2,v+1,v+0的值到栈内,而++v,++v,++v会先对v自加3次,最后压栈的是v+3的值。
问题2: 参数压栈的顺序问题
与上面的一样,这里自己定义一个函数,看上去更直观一点。
1 |
|
根据上一节的结论,v++压入堆栈的是不同的值,但是如果你认为形参a 与 b的值是1和2那就错了,正确答案是2和1。
之所以是这样,是因为这里的入栈顺序是 从右往左 ,并且由于栈的特性,最先push进去的最后一个pop出来。故首先push v++也就是v的值,接着push 的是v+1,出栈时第一个pop 最后 push 的值,得到2,然后再pop 得到1。
有的人肯定会想,是不是所有的情况下函数参数传递都是从右往左的? 以下是我查了资料后的整理:
函数的参数传递有4种途径:
寄存器传递 (由于寄存器的个数是有限的,不常见)
约定存储单元传递
利用堆栈传递
利用CALL后续区传递
据说C/C++的函数参数压栈顺序是从右往左,而pascal是从左往右。
除此之外,不同的调用约定压栈顺序不同,C/C++的默认调用约定是__cdecl
,系统API中__stdcall
比较常见,顺序都是上面所说的右到左,另外还有个__fastcall
,它的前两个参数通过寄存器传递(从左至右)。
希望对部分有着同样问题的同学有帮助。
Copyright © 雪峰 2015