C语言函数调用过程

目录

C语言的函数调用过程

先上一段代码

#include<stdio.h>
int Add(int x, int y)
{
    int z = 0;
    z = x + y;
    return z;
}
#include <stdio.h>
int main()
{
    int a = 10;
    int b = 20;
    int c = Add(a, b);
    return 0;
}

这个函数计算两个数的值。接下来我们通过反汇编,来看看函数被后的过程。

反汇编代码

#include <stdio.h>
int main()
{
01151720  push        ebp  
01151721  mov         ebp,esp  
01151723  sub         esp,0E4h  
01151729  push        ebx  
0115172A  push        esi  
0115172B  push        edi  
0115172C  lea         edi,[ebp-0E4h]  
01151732  mov         ecx,39h  
01151737  mov         eax,0CCCCCCCCh  
0115173C  rep stos    dword ptr es:[edi]  
    int a = 10;
0115173E  mov         dword ptr [a],0Ah  
    int b = 20;
01151745  mov         dword ptr [b],14h  
    int c = Add(a, b);
0115174C  mov         eax,dword ptr [b]  
0115174F  push        eax  
01151750  mov         ecx,dword ptr [a]  
01151753  push        ecx  
01151754  call        _Add (01151104h)  
01151759  add         esp,8  
0115175C  mov         dword ptr [c],eax  
    return 0;
0115175F  xor         eax,eax  
}
01151761  pop         edi  
01151762  pop         esi  
01151763  pop         ebx  
01151764  add         esp,0E4h  
0115176A  cmp         ebp,esp  
0115176C  call        __RTC_CheckEsp (01151122h)  
01151771  mov         esp,ebp  
01151773  pop         ebp  
}
//Add()
#include<stdio.h>
int Add(int x, int y)
{
001016D0  push        ebp  
001016D1  mov         ebp,esp  
001016D3  sub         esp,0CCh  
001016D9  push        ebx  
001016DA  push        esi  
001016DB  push        edi  
001016DC  lea         edi,[ebp-0CCh]  
001016E2  mov         ecx,33h  
001016E7  mov         eax,0CCCCCCCCh  
001016EC  rep stos    dword ptr es:[edi]  
    int z = 0;
001016EE  mov         dword ptr [z],0  
    z = x + y;
001016F5  mov         eax,dword ptr [x]  
001016F8  add         eax,dword ptr [y]  
001016FB  mov         dword ptr [z],eax  
    return z;
001016FE  mov         eax,dword ptr [z]  
}
00101701  pop         edi  
00101702  pop         esi  
00101703  pop         ebx  
00101704  mov         esp,ebp  
00101706  pop         ebp  
00101707  ret  

以上就是反汇编代码。现在我们一段一段的进行解读,去看看整个程序是怎么执行的。

main()函数的创建

01151720  push        ebp  
01151721  mov         ebp,esp  
01151723  sub         esp,0E4h  
01151729  push        ebx  
0115172A  push        esi  
0115172B  push        edi  
0115172C  lea         edi,[ebp-0E4h]  
01151732  mov         ecx,39h  
01151737  mov         eax,0CCCCCCCCh  
0115173C  rep stos    dword ptr es:[edi]  
//分割线.........................
int a = 10;
0115173E  mov         dword ptr [a],0Ah  
    int b = 20;
01151745  mov         dword ptr [b],14h 

这里要给读者朋友们简单说一下变量的含义。ebp 和 esp 是两个通用寄存器。它们可以存储 立即数内存地址。esi是源变址寄存器。edi是目的变址寄存器。
它们都可用来储存地址。

在main()函数之前有一个函数CRTStartUp,ebp为这个函数的栈底,esp为这个函数的栈顶。
这里写图片描述
接下来通过前两句代码mov 赋值移动,它们的状态变成了如下:
这里写图片描述
第三句话中,sub是减法指令,这句话使得esp向后移动了0E4h个单位。
接下来的三句话,是将ebx,esi,edi压入了栈当中。
这里写图片描述
第七句话,lea 的意思是load effective address,也就是将[ebp - 0E4h]的地址取出来,赋值给了edi。
第八句话,将39h赋值移动到ecx。第九句话,将内容赋值移动给eax。
第十句话,rep stos 是重复拷贝数据。
注意:
ecx寄存器是用来保存复制粘贴的次数,ecx是计数器。
eax寄存器是用来传送信息的,eax是累加器,通常用来存储数据。
rep 是重复上述指令。
stos 是将eax的值赋值移动给 es:[edi]这个地址的存储单元。
这里写图片描述
现在其中的空白部分被cc cc cc cc充满了。这里cc cc就是经常遇到的“烫烫”。
分割线
接下来的操作很好理解,其目的是将 a和b创建,并且压入栈中。
这里写图片描述

Add()函数的调用过程

    int c = Add(a, b);
0115174C  mov         eax,dword ptr [b]  
0115174F  push        eax  
01151750  mov         ecx,dword ptr [a]  
01151753  push        ecx  
01151754  call        _Add (01151104h)  
01151759  add         esp,8  
0115175C  mov         dword ptr [c],eax  

前四句话,它将a和b找了个寄存器存起来,与此同时,以函数参数列表倒着的方式压入栈中。
这里写图片描述
方便起见,下面的图将只截取上半部分。

接下来使用了call指令,这个指令是先将 指令 移动到下一句话,然后再操纵call 后面的内容。在call处按F11我们将跳转到Add()函数。
这里写图片描述
Add()函数


#include<stdio.h>
int Add(int x, int y)
{
001016D0  push        ebp  
001016D1  mov         ebp,esp  
001016D3  sub         esp,0CCh  
001016D9  push        ebx  
001016DA  push        esi  
001016DB  push        edi  
001016DC  lea         edi,[ebp-0CCh]  
001016E2  mov         ecx,33h  
001016E7  mov         eax,0CCCCCCCCh  
001016EC  rep stos    dword ptr es:[edi]  

这一部分和main函数创建过程非常相似。所以用俩图来表示。
这里写图片描述
这里写图片描述

    int z = 0;
001016EE  mov         dword ptr [z],0  
    z = x + y;
001016F5  mov         eax,dword ptr [x]  
001016F8  add         eax,dword ptr [y]  
001016FB  mov         dword ptr [z],eax  
    return z;
001016FE  mov         eax,dword ptr [z]  
}
00101701  pop         edi  
00101702  pop         esi  
00101703  pop         ebx  
00101704  mov         esp,ebp  
00101706  pop         ebp  
00101707  ret  

这段代码非常有意思,请读者朋友们一定要注意。
首先,我们创建了变量 ptr[z]。然后,将ptr[x]的值放在eax里边,将ptr[y]的值与eax相加后再放在eax里,将eax的值赋值移动到 ptr[z]中。最后,我们需要return z。这个地方编译器再次将ptr[z]的值赋值移动到了eax。这么做的原因是:ptr[x]、ptr[y]、ptr[z]将在函数完成后就会销毁,但是寄存器eax不会被销毁,所以在eax的值非常安全。

括号外的第四句话非常重要。它将销毁该函数的所有数据。
这里写图片描述
将ebp弹出后,该点只有一个指针esp。接下来执行ret,即 return 返回。返回到我们刚才call下面的地方。

main()函数的销毁

01151759  add         esp,8  
0115175C  mov         dword ptr [c],eax  
    return 0;
0115175F  xor         eax,eax  
}
01151761  pop         edi  
01151762  pop         esi  
01151763  pop         ebx  
01151764  add         esp,0E4h  
0115176A  cmp         ebp,esp  
0115176C  call        __RTC_CheckEsp (01151122h)  
01151771  mov         esp,ebp  
01151773  pop         ebp  
}

现在我们跳回到了该段的第一句话,现在esp + 8向高地址进发了。其目的就是消去了形参实例化的a,b。第二句话,我们将eax里保存的数据赋值移动给了c。最后,xor异或,自己和自己异或 其值变为0。

这里写图片描述
之后,我们出栈了 edi,esi,ebx。现在esp指向了最开始我们红色那个箭头 ebp - 0E4h
然后,由于add操作,我们继续向高地址进发,到了我们最开始的地方。
这里写图片描述
这里写图片描述
现在,我们到了cmp指令,cmp是比较指令。它的操作是拿第一个数减去第二个数,如果相减为ZF=0说明相等。但是cmp并不会真的减。到此位置main()函数就真的执行完毕,然后esp,ebp。回到了原来的位置。至于后面的函数调用,并不需要深究。至此我们就看到了在汇编上函数如何实现的

谢谢您的阅读,如有问题,请多指教。

  • 13
    点赞
  • 53
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值