浅谈函数的调用过程

在我们学习到C语言函数这一部分时,一定会对函数的调用有一点疑问,下面我将通过一段代码让大家简单了解“main”函数的调用过程

#define _CRT_SECURE_NO_WARNINGS 1

#include<stdio.h>

int Add(int x, int y)
{
    int z = 0;
    z = x + y;
    return z;
}




int main()
{
    int a = 10;
    int b = 20;
    int c = 0;
    c = Add(a, b);
    printf("%d\n", c);
    system("pause");
    return 0;
}

首先我们进入调试过程并打开“调试—-窗口—-调用堆栈” 窗口

这里写图片描述
当我们一步一步进行调试 进入到“Add函数”当中时,我们发现在调用堆栈窗口顶部出现了“Add函数”
这里写图片描述
而当我们退出“Add函数”时,调用堆栈窗口顶部的“Add函数”则消失了,由此可见在运行过程中,每下层的函数都会调用上层的函数。
这里写图片描述

所以我们观察到main函数是在下层的 __tmainCRTStartup() 函数被调用,而__tmainCRTStartup() 函数则是被更下一层的 “mainCRTStartup()函数”调用。每次的函数调用都是一个过程,这个过程我们通常称之为:函数的调用过程。这个过程要为函数开辟空间,用于本次函数的调用中临时变量的保存、现场保护,这块栈空间我们称之为函数栈帧。

首先我们进入到“main”函数的反汇编中

int main()
{
00B01420  push        ebp  
00B01421  mov         ebp,esp  
00B01423  sub         esp,0E4h  
00B01429  push        ebx  
00B0142A  push        esi  
00B0142B  push        edi  
00B0142C  lea         edi,[ebp-0E4h]  
00B01432  mov         ecx,39h  
00B01437  mov         eax,0CCCCCCCCh  
00B0143C  rep stos    dword ptr es:[edi]  
    int a = 10;
00B0143E  mov         dword ptr [a],0Ah  
    int b = 20;
00B01445  mov         dword ptr [b],14h  
    int c = 0;
00B0144C  mov         dword ptr [c],0  
    c = Add(a, b);
00B01453  mov         eax,dword ptr [b]  
00B01456  push        eax  
00B01457  mov         ecx,dword ptr [a]  
00B0145A  push        ecx  
    c = Add(a, b);
00B0145B  call        _Add (0B010E6h)  
00B01460  add         esp,8  
00B01463  mov         dword ptr [c],eax 

可以看到在“main”函数定义变量之前,程序进行了一些操作,我们来逐条分析这些操作的作用。

00B01420  push        ebp  
00B01421  mov         ebp,esp  
00B01423  sub         esp,0E4h  
00B01429  push        ebx  
00B0142A  push        esi  
00B0142B  push        edi 

在没有调用main函数之前 main函数与 __tmainCRTStartup() 维护同一片空间,
第一条语句push 表示压栈,将ebp放入栈顶,第二条语句,mov 表示将esp移动到ebp的位置,也就是esp从①位置移动到②位置,第三条语句sub 表示esp减去一个字节数(0E4h),从而往上移动第四、第五、第六条语句表示将ebp,esi,edi依次压入栈顶,并移动esp。

这里写图片描述

00B0142C  lea         edi,[ebp-0E4h]  
00B01432  mov         ecx,39h  
00B01437  mov         eax,0CCCCCCCCh  
00B0143C  rep stos    dword ptr es:[edi] 

这四条语句表示把eax的内容重复ecx次,起始地址为edi所指向的地址
这里写图片描述

    int a = 10;
00B0143E  mov         dword ptr [a],0Ah  
    int b = 20;
00B01445  mov         dword ptr [b],14h  
    int c = 0;
00B0144C  mov         dword ptr [c],0  

第一条汇编代码表示将” 0Ah (10)”放入到 ebp-8(a)的地址中
第二条表示将“14h”放入到 ebp-20(b) 的地址中
第三条表示将“0”放入到 ebp-32(c) 的地址中
这里写图片描述

这里写图片描述

这里写图片描述

    c = Add(a, b);
00B01453  mov         eax,dword ptr [b]  
00B01456  push        eax  
00B01457  mov         ecx,dword ptr [a]  

    c = Add(a, b);
00B0145A  push        ecx  

表示将b,a分别压栈并传参。
进入到Add函数中:

int Add(int x, int y)
{
00B013D0  push        ebp  
00B013D1  mov         ebp,esp  
00B013D3  sub         esp,0CCh  
00B013D9  push        ebx  
00B013DA  push        esi  
00B013DB  push        edi  
00B013DC  lea         edi,[ebp-0CCh]  
00B013E2  mov         ecx,33h  
00B013E7  mov         eax,0CCCCCCCCh  
00B013EC  rep stos    dword ptr es:[edi]  
    int z = 0;
00B013EE  mov         dword ptr [z],0  
    z = x + y;
00B013F5  mov         eax,dword ptr [x]  
00B013F8  add         eax,dword ptr [y]  
00B013FB  mov         dword ptr [z],eax  
    return z;
00B013FE  mov         eax,dword ptr [z]  
}
00B01401  pop         edi  
00B01402  pop         esi  
00B01403  pop         ebx  
00B01404  mov         esp,ebp  
00B01406  pop         ebp  
00B01407  ret 

前面与之前所说的一致,表示开辟Add函数的栈空间,并存储z的值。然后表示将传来的两个参数之和相加放到eax中 也就是z的地址中。
pop表示出栈,依次将edi,esi,ebx出站后,将指向栈低的ebp的值移动到栈顶 的esp处,ebp出栈,程序走到ret时,销毁堆栈,esp回到main函数的栈顶,运算结果得到返回。

00B01460  add         esp,8  
00B01463  mov         dword ptr [c],eax 

esp向下移动,形参销毁,再将eax的值赋给C。销毁main函数堆栈,程序结束。

阅读更多
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页