【源程序】
#include <stdio.h>
void print(int a, int b, int c)
{
printf("%d/t%d/t%d/n", a, b, c);
}
int main(void)
{
int p = 0;
print(p++, ++p, p++);
return0;
}
【调用分析】
其实大家应该都知道这个顺序了,我简单粗略的提一下吧。
C 和 C++ 编译器进行函数调用的时候参数压栈的顺序时从右向左的。拿上面的程序来说,首先压栈的参数是最后一个“p++”,压栈的值应该是0,此时 p=1;然后压栈参数的是中间的“++p”,压栈的值是2,此时 p=2;最后压栈的是第一个“p++”,压栈的值应该是 2,此时p=3。
那么,输出结果就应该是“2 2 0”。
【存在问题】
在 Tc、Tc3、GCC 下编译通过,运行结果“2 2 0”;
在 VC.net 下编译通过,运行结果“2 3 0”;
【问题分析】
1、Tc3下从压栈到函数调用阶段的汇编代码:(使用方法 tcc -S filename.cpp)
;
; {
; int p = 0;
;
xor si,si ;将 si 清零,即 p=0
;
; print(p++, ++p, p++);
;
;将最后一个参数压栈,先压栈,后加1
mov ax,si ;ax=0
inc si
push ax
;将中间的参数压栈,先加1,后压栈
inc si
mov ax,si ;ax=2
push ax
;将第一个参数压栈,先压栈,后加1
mov ax,si ;ax=2
inc si
push ax
;调用 print 函数
call near ptr @print$qiii
至此,在 Tc 和 Tc3 下的分析工作完成,结果是“2 2 0”的原因也就很清楚了。
2、Vc.net下从压栈到函数调用阶段的汇编代码:
;
;int p = 0;
;
00411B3E mov dword ptr [p],0 ;赋初值 0,即 p=0
;
;print(p++, ++p,p++);
;
;这里涉及到了一些地址和寄存器,可能有的读者读不懂,把他们理解为变量就行了
;
;文中共出现 3 个地址 [p] [ebp-0D0h] [ebp-0D4h] , ebp 和 p 在段程序中的值
;没有改变,所以地址也可以理解为一个变量。下面对一个地址进行解释
; mov dword ptr [p],0 就是把 p 地址处的内存赋值为 0,赋值单元数 dword
;
;文中还有一些寄存器,只是进行运算的时候使用,可以理解为临时变量
;
;操作第三个参数
00411B45 mov eax,dword ptr [p] ;eax = 0 即 eax = p
00411B48 mov dword ptr [ebp-0D0h],eax ;存放最第三个参数值
00411B4E mov ecx,dword ptr[p] ;ecx = 0 即 ecx = p
00411B51 add ecx,1 ;ecx = 1
00411B54 mov dword ptr [p],ecx ;p = 1 即 p = ecx
;操作第二个参数
00411B57 mov edx,dword ptr [p] ;edx = 1 即 edx = p
00411B5A add edx,1 ;edx = 2
00411B5D mov dword ptr [p],edx ;p = 2 即 p = edx
00411B60 mov eax,dword ptr[p] ;eax = 2 即 p = eax
00411B63 mov dword ptr [ebp-0D4h],eax ;存放第二个参数值
;操作第一个参数
00411B69 mov ecx,dword ptr[p] ;ecx = 2 即 ecx = p
00411B6C add ecx,1 ;ecx = 3
00411B6F mov dword ptr [p],ecx ;p = 3 即 p = ecx
;开始压栈
00411B72 mov edx,dword ptr [ebp-0D0h] ;第三个参数压栈,值为 0
00411B78 push edx
00411B79 mov eax,dword ptr [p] ;第二个参数压栈,值为3
00411B7C push eax
00411B7D mov ecx,dword ptr[ebp-0D4h] ;第一个参数压栈,值为 2
00411B83 push ecx
;调用 print 函数
00411B84 call print(41100Fh)
00411B89 add esp,0Ch
至此,在 Vc.net 下的分析工作完成,结果是“2 3 0”的原因是压栈的顺序不是标准 C 语言中约定的压栈的顺序,或者说根本就不是顺序,至于为什么这样我也不清楚,如果 .Net 的开发人员注意到了这个问题的话回个贴吧(痴人说梦^_^)。
在C99中找到相应的建议了,是没有明确的规定压栈的顺序。
The order of evaluation of the function designator, the actual arguments, and subexpressions within the actual arguments is unspecified, but there is a sequence point before the actual call.
EXAMPLE In the function call (*pf[f1()]) (f2(), f3() + f4()) the functions f1, f2, f3, and f4 may be called in any order. All side effects have to be completed before the function pointed to by pf[f1()] is called.
【建议】
为了你的程序的通用性,不要写那些模棱两可的程序,尽管有很多考试都考这些,我个人不认为把程序写成这样是高手。