偶然发现了这样一个有意思的C语言程序:
这个程序有意思之处在于,从源程序中分析出的结果与程序运行得到的结果差别很大。一个很自然的想法是,我看到源程序就能知道运行的结果,很郁闷的是,我看到这个程序运行的结果后,疑问就更多了。。。程序的实参是同一个变量,而且在传递参数的过程中有计算,那么在传递过程中两个实参肯定是相互影响的。至于参数是怎么互相影响的,取决于编译器怎么翻译这段程序到汇编语言(有人说不同的编译器翻译这段程序的汇编语言的不一样的,所以执行的结果也不一样,这点不清楚)
这里贴上一个错误的分析结果,注释后面的输出为错误结果,相信不少人都这样分析过这个程序:
#include <stdio.h>
int func(int a,int b)
{
printf("%d,%d\n",a,b);
int c;
c=a+b;
return c;
}
int main(void)
{
int x=6,r;
r=func(x++,x++);//6,6
错误分析的结果,忽视了参数传递前的计算
printf("%d\n",x);//8
printf("%d\n\n",r);//12
x = 6;
r=func(x++,++x);//7,7
错误分析的结果,这里以为程序先计算++x,得到计算结果x=7后入栈,把此时的值传递给b形参,
然后传递x=7给a形参,计算x++得到x=8。可是编译器可不是这么想的!
printf("%d\n",x);//8
printf("%d\n\n",r);//14
x=6;
r=func(++x,++x);//8,7
错误分析的结果,这里以为程序先计算++x,得到计算结果x=7后入栈,把此时的值传递给b形参,
然后计算另一个++x,得到计算结果x=8后入栈,把此时的值传递给a形参。
可是,编译器是先处理计算,然后把计算过程中需要入栈的值保存起来,计算结束后统一入栈参数,这样的过程才符合机器处理的思路。
printf("%d\n",x);//8
printf("%d\n\n",r);//15
x=6;
r=func(++x,x++);//8,6
错误分析的结果,这里以为程序先把x=6入栈传递给b参数,再计算x++得到x=7,
然后计算++x得到x=8,把此时的结果传递给a参数。
貌似分析的很正确,这只是一种巧合的思路。
printf("%d\n",x);//8
printf("%d\n\n",r);//13
}
C语言中参数的传递过程是通过堆栈实现的(几乎所有的高级语言都通过堆栈传递参数),每个函数(包括主函数)都要创建堆栈框架,堆栈框架是为传递的参数、子例程的返回地址、局部变量和保存的寄存器保留的堆栈空间。
实参是主函数中的局部变量或者是这个局部变量经过计算后得到的值。下面是把函数反编译后都会出现的汇编语句:
push ebp ; EBP寄存器为基址寄存器,是构建堆栈框架重要的寄存器
mov ebp,esp ; 这里设定EBP寄存器为基址
sub esp,0E0h ; ESP寄存器与EBP基址寄存器相差了E0h个字节
这样的话,ebp与esp之间的堆栈空间可以存储局部变量,比esp更小的地址可以存放传递的参数、返回地址和保存的寄存器。
这里使用的是VS2010编译器,它对这个C语言程序参数的处理过程如下:
#include <stdio.h>
int func(int a,int b)
{
printf("%d,%d\n",a,b);
int c;
c=a+b;
return c;
}
int main(void)
{
int x=6,r;
r=func(x++,x++);//7,6
/*编译器(先保存x=6后计算得到x=7,再保存x=7后计算得到x=8),
然后先入栈计算过程中保存的x=6,再入栈计算过程中保存的x=7,
调用子函数后,把后入栈的x=7传递给a形参,把先入栈的x=6传递给b形参。*/
printf("%d\n",x);//8
printf("%d\n\n",r);//13
x = 6;
r=func(x++,++x);//7,8
/*编译器(先计算得到x=7后保存x=7,再计算得到x=8),
然后先入栈计算结果x=8,再入栈计算过程中保存的x=7,
调用子函数后,把后入栈的x=7传递给a形参,把先入栈的x=8传递给b形参。*/
printf("%d\n",x);//8
printf("%d\n\n",r);//15
x = 6;
r=func(++x,++x);//8,8
/*编译器(先计算得到x=8,再计算得到x=8),
然后先入栈计算结果x=8,再入栈计算结果x=8,
调用子函数后,把后入栈的x=8传递给a形参,把先入栈的x=8传递给b形参。*/
printf("%d\n",x);//8
printf("%d\n\n",r);//16
x = 6;
r=func(++x,x++);//8,6
/*编译器(先保存x=6后计算得到x=7,再计算得到x=8),
然后先入栈计算过程中保存的x=6,在入栈计算结果x=8,
调用子函数后,把后入栈的x=8传递给a形参,把先入栈的x=6传递给b形参。*/
printf("%d\n",x);//8
printf("%d\n\n",r);//14
}
下面是反汇编的代码:
(节选第一部分:)
11: int x=6,r;
mov dword ptr [x],6
12: r=func(x++,x++);//7,6
mov eax,dword ptr [x]
mov dword ptr [ebp-0DCh],eax
mov ecx,dword ptr [x]
add ecx,1
mov dword ptr [x],ecx
mov edx,dword ptr [x]
mov dword ptr [ebp-0E0h],edx
mov eax,dword ptr [x]
add eax,1
mov dword ptr [x],eax
mov ecx,dword ptr [ebp-0DCh]
push ecx
mov edx,dword ptr [ebp-0E0h]
push edx
call func (0B101Eh)
add esp,8
mov dword ptr [r],eax
13: /*编译器(先保存x=6后计算得到x=7,再保存x=7后计算得到x=8),
14: 然后先入栈计算过程中保存的x=6,再入栈计算过程中保存的x=7,
15: 调用子函数后,把后入栈的x=7传递给a形参,把先入栈的x=6传递给b形参。*/
(节选第二部分:)
18: x = 6;
mov dword ptr [x],6
19: r=func(x++,++x);//7,8
mov eax,dword ptr [x]
add eax,1
mov dword ptr [x],eax
mov ecx,dword ptr [x]
mov dword ptr [ebp-0DCh],ecx
mov edx,dword ptr [x]
add edx,1
mov dword ptr [x],edx
mov eax,dword ptr [x]
push eax
mov ecx,dword ptr [ebp-0DCh]
push ecx
call func (0B101Eh)
add esp,8
mov dword ptr [r],eax
20: /*编译器(先计算得到x=7后保存x=7,再计算得到x=8),
21: 然后先入栈计算结果x=8,再入栈计算过程中保存的x=7,
22: 调用子函数后,把后入栈的x=7传递给a形参,把先入栈的x=8传递给b形参。*/
(节选第三部分:)
25: x = 6;
mov dword ptr [x],6
26: r=func(++x,++x);//8,8
mov eax,dword ptr [x]
add eax,1
mov dword ptr [x],eax
mov ecx,dword ptr [x]
add ecx,1
mov dword ptr [x],ecx
mov edx,dword ptr [x]
push edx
mov eax,dword ptr [x]
push eax
call func (0B101Eh)
add esp,8
mov dword ptr [r],eax
27: /*编译器(先计算得到x=8,再计算得到x=8),
28: 然后先入栈计算结果x=8,再入栈计算结果x=8,
29: 调用子函数后,把后入栈的x=8传递给a形参,把先入栈的x=8传递给b形参。*/
(节选第四部分:)
32: x = 6;
mov dword ptr [x],6
33: r=func(++x,x++);//8,6
mov eax,dword ptr [x]
mov dword ptr [ebp-0DCh],eax
mov ecx,dword ptr [x]
add ecx,1
mov dword ptr [x],ecx
mov edx,dword ptr [x]
add edx,1
mov dword ptr [x],edx
mov eax,dword ptr [ebp-0DCh]
push eax
mov ecx,dword ptr [x]
push ecx
call func (0B101Eh)
add esp,8
mov dword ptr [r],eax
34: /*编译器(先保存x=6后计算得到x=7,再计算得到x=8),
35: 然后先入栈计算过程中保存的x=6,在入栈计算结果x=8,
36: 调用子函数后,把后入栈的x=8传递给a形参,把先入栈的x=6传递给b形参。*/
以上只是简单的描述了编译器处理上述程序的过程,在(2)中从汇编中跟踪分析堆栈框架的作用和参数的传递过程