从汇编角度看函数参数传递

基本理论:

  • 函数参数传递机制问题在本质上是调用函数过程和被调用函数在调用发生时进行同的方法问题。基本的参数传递机制有两种,值传递、引用传递。

    • 值传递: 在值传递过程中,被调函数的形参作为被调函数的局部变量,即在该函数栈中开辟内存空间以存放由主函数传递进来的实参的值,从而成为实参的一个副本。值传递的特点是被调函数对形参的任何操作都是作为局部变量进行,不会影响主函数实参的值。

    • 引用传递:引用传递过程中,被调函数的形参虽然也作为局部变量在该函数栈中开辟了内存空间,但这时存放的是由主函数传递进来的实参的地址。被调函数的形对形参的任何操作都被处理成间接寻址,即通过在栈中存放的地址访问主函数中的实参变量。因此,被调函数对形参做的任何操作都会影响主函数中的实参变量。

  • 在C语言中,值传递是唯一可用的参数传递机制。但这时我们就要问,不是还有指针的地址传递吗?由于受指针变量作为函数参数的影响,有许多朋友认为指针作参数传递和引用传递一样。这是错误的。请看下面的代码:

int swap(int *x, int *y)
{
   int temp;
   temp = *x;
   *x = *y; 
   *y = temp;
   return temp;
}
int main()
{
    int a = 1, b = 2;
    int *p1 = &a;
    int *p2 = &b;
    swap(p1, p2);
    printf("a=%d  b=%d\n", a, b);
    return 0;
}
  • 函数swap以两个指针变量作为参数,当main()调用swap时,是以值传递的方式将指针变量p1、p2的值(也就是变量a、b的地址)放在了swap在堆栈中为形式参数x、y开辟的内存单元中。这一点从以下的汇编代码可以看出:
//main函数汇编
int main()
{
00271420  push        ebp
00AA1421  mov         ebp,esp  
00AA1423  sub         esp,0F4h //主函数栈桢空间总大小f4 --->244  
00AA1429  push        ebx  
00AA142A  push        esi  
00AA142B  push        edi  
00AA142C  lea         edi,[ebp-0F4h]  
00AA1432  mov         ecx,3Dh  
00AA1437  mov         eax,0CCCCCCCCh //将内存空间循环赋予初始值cccccccc 
00AA143C  rep stos    dword ptr es:[edi]  
00AA143E  mov         eax,dword ptr ds:[00AA8000h]  
00AA1443  xor         eax,ebp  
00AA1445  mov         dword ptr [ebp-4],eax  
    int a = 10, b = 20;
00AA1448  mov         dword ptr [a],0Ah //a内存空间放入10
00AA144F  mov         dword ptr [b],14h //b内存空间放入20 
    int *p1 = &a;
00AA1456  lea         eax,[a]            //将a的地址放入eax  
00AA1459  mov         dword ptr [p1],eax //将eax里面的值放入p1内存空间中,下面类似 
    int *p2 = &b;
00AA145C  lea         eax,[b]  
00AA145F  mov         dword ptr [p2],eax  
    swap(p1, p2);
00AA1462  mov         eax,dword ptr [p2] //参数p2的值进栈  
00AA1465  push        eax  
00AA1466  mov         ecx,dword ptr [p1] //参数p1的值进栈  
00AA1469  push        ecx  
00AA146A  call        swap (0AA100Fh)    //调用swap函数 
00AA146F  add         esp,8              //清理栈中的参数
    printf("a=%d  b=%d\n", a, b);//下面是printf函数的汇编
00AA1472  mov         esi,esp  
00AA1474  mov         eax,dword ptr [b]  
00AA1477  push        eax  
00AA1478  mov         ecx,dword ptr [a]  
00AA147B  push        ecx  
00AA147C  push        0AA5858h  
00AA1481  call        dword ptr ds:[0AA9114h]  
00AA1487  add         esp,0Ch  
00AA148A  cmp         esi,esp  
00AA148C  call        __RTC_CheckEsp (0AA113Bh)  
    return 0;
00AA1491  xor         eax,eax  
}
  • 阅读上述代码要注意,INTEL80x86系列的CPU对堆栈的处理是向下生成,即从高地址单元向低地址单元生成。从上面的汇编代码可知,main()在调用swap之前,先将实参的值按从右至左的顺序压栈,即先p2进栈,再p1进栈。调用结束之后,主调函数main()负责清理堆栈中的参数。Swap 将使用这些进入堆栈的变量值。
//swap函数的汇编代码:
int swap(int *x, int *y)
{
002713C0  push        ebp  
002713C1  mov         ebp,esp  
002713C3  sub         esp,0CCh//swap函数栈桢空间大小cc ---> 204  
002713C9  push        ebx  
002713CA  push        esi  
002713CB  push        edi  
002713CC  lea         edi,[ebp-0CCh]
00AA13D2  mov         ecx,33h  
00AA13D7  mov         eax,0CCCCCCCCh//将内存空间循环赋予初始值cccccccc  
00AA13DC  rep stos    dword ptr es:[edi]  
    int temp;
    temp = *x;
00AA13DE  mov         eax,dword ptr [x]//操作已存放在堆栈中的p1,将p1置入eax 
00AA13E1  mov         ecx,dword ptr [eax]//通过寄存器间址将*p1置入ecx  
00AA13E3  mov         dword ptr [temp],ecx//经由ecx将*p1置入temp变量的内存单元。以下类似 
    *x = *y;
00AA13E6  mov         eax,dword ptr [x]  
00AA13E9  mov         ecx,dword ptr [y]  
00AA13EC  mov         edx,dword ptr [ecx]  
00AA13EE  mov         dword ptr [eax],edx  
    *y = temp;
00AA13F0  mov         eax,dword ptr [y]  
00AA13F3  mov         ecx,dword ptr [temp]  
00AA13F6  mov         dword ptr [eax],ecx   
}
  • 由上述汇编代码基本上说明了C语言中值传递的原理,只不过传递的是指针的值。本文后面还要论述使用引用传递的swap函数。从这些汇编代码分析,这里我们可以得到以下几点:

    • 进程的堆栈存储区是主调函数和被调函数进行通信的主要区域。
    • C语言中参数是从右向左进栈的。
    • 被调函数使用的堆栈区域结构为:局部变量(如temp)、返回地址、函数参数、低地址 、高地址。
    • 由主调函数在调用后清理堆栈。
    • 函数的返回值一般是放在寄存器中的。

    • 这里尚需补充说明几点:一是参数进栈的方式。对于内部类型,由于编译器知道各类型变量使用的内存大小故直接使用push指令;对于自定义的类型(如structure),采用从源地址向目的(堆栈区)地址进行字节传送的方式入栈。二是函数返回值为什么一般放在寄存器中,这主要是为了支持中断;如果放在堆栈中有可能因为中断而被覆盖。三是函数的返回值如果很大,则从栈向存放返回值的地址单元(由主调函数在调用前将此地址压栈提供给被调函数)进行字节传送,以达到返回的目的。如果在被调函数中返回局部变量的地址是毫无意义的;因为局部变量存于栈中,调用结束后栈将被清理,这些地址就变得无效了。

  • C++既有C的值传递又有引用传递。在值传递上与C一致,这里着重说明引用传递。如本文前面所述,引用传递就是传递变量的地址到被调函数使用的堆栈中。在C++中声明引用传递要使用”&”符号,而调用时则不用。

代码:

int& swap2(int& x, int& y) 
{
   int temp;
   temp = x;
   x = y;
   y = temp;
   return x;
}
void main()
{
   int a = 1, b = 2;
   swap2(a, b);
}
main函数汇编

 void main()
 {
……
……
    int a = 1, b = 2;
00401088   mov         dword ptr [ebp-4],1 ;变量a
0040108F   mov         dword ptr [ebp-8],2 ;变量b
    swap2(a, b);
00401096   lea         eax,[ebp-8] //将b的偏移地址送入eax
00401099   push        eax //b的偏移地址压栈
0040109A   lea         ecx,[ebp-4] //将a的偏移地址送入ecx
0040109D   push        ecx //将a的偏移地址压栈
0040109E   call        @ILT+20(swap2) (00401019) //调用swap函数
004010A3   add         esp,8 //清理堆栈中的参数
……
……

}
  • 可以看出,main函数在调用swap2之前,按照从右至左的顺序将b和a的偏移地址压栈,这就是在传递变量的地址swap(int &x, int &y)
//swap函数汇编
int& swap2(int& x, int& y)
{
00401030   push        ebp
00401031   mov         ebp,esp
……
……
    int temp;
    temp = x;
00401048   mov         eax,dword ptr [ebp+8]
0040104B   mov         ecx,dword ptr [eax]
0040104D   mov         dword ptr [ebp-4],ecx    
    x = y;
00401050   mov         edx,dword ptr [ebp+8]
00401053   mov         eax,dword ptr [ebp+0Ch]
00401056   mov         ecx,dword ptr [eax]
00401058   mov         dword ptr [edx],ecx 
    y = temp;
0040105A   mov         edx,dword ptr [ebp+0Ch]
0040105D   mov         eax,dword ptr [ebp-4]
00401060   mov         dword ptr [edx],eax
    return x;
00401062   mov         eax,dword ptr [ebp+8] ;返回x,由于x是外部变量的偏移地址,故返回是合法的
9: }
  • 可以看出,swap2与前面的swap函数的汇编代码相似。这是因为前面的swap函数接受指针变量,而指针变量的值正是地址。所以,对于这里的swap2和前面的swap来讲,栈中的函数参数存放的都是地址,在函数中操作的方式是一致的。但是,对swap2来说这个地址是主调函数通过将实参变量的偏移地址压栈而传递进来的—-这是引用传递。而对swap来说,这个地址是主调函数通过将实参变量的值压栈而传递进来的–这是值传递,只不过由于这个实参变量是指针变量所以其值是地址而已。这里的关键点在于,同样是地址,一个是引用传递中的变量地址,一个是值传递中的指针变量的值。我想若能明确这一点,就不至于将C语言中的以指针变量作为函数参数的值传递情况混淆为引用传递了。虽然x是一个局部变量,但是由于其值是主调函数中的实参变量的地址,故在swap2中返回这个地址是合法的。
  • c++ 中经常使用的是常量引用,如将swap2改为:Swap2(const int& x; const int& y),这时将不能在函数中修改引用地址所指向的内容,具体来说,x和y将不能出现在”=”的左边。

猜你喜欢

转载自blog.csdn.net/magic_world_wow/article/details/80582144