一个C语言程序说明了实参到形参的传递过程(1)

          偶然发现了这样一个有意思的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)中从汇编中跟踪分析堆栈框架的作用和参数的传递过程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值