浅析函数的调用过程

本文详细解析了C语言中函数调用的过程,包括栈帧的创建与销毁,以及涉及的寄存器使用方式。通过具体的Add函数实例,展示了如何在内存中为函数调用分配空间,并说明了栈帧在函数执行中的关键作用。

函数的调用过程,栈帧的创建和销毁


在c语言中到某一个函数时,它就会跳转过去执行这个函数,执行完毕后接着再去执行下一条指令。在执行调用函数的过程中,计算机通常还要根据函数完成一些工作,这些操作通过形成一个栈帧来完成。栈帧是编译器用来实现函数调用过程的一种数据结构。C语言中,每个栈帧对应着一个未运行完的函数。

以Add()函数为例研究一下函数的调用过程。

#include<stdio.h>
#include<stdlib.h>
int Add(int a, int b){
    int z = 0;
    z = a + b;
    return z;
}
int main(){
    int a = 10;
    int b = 20;
    int ret;
    ret = Add(a, b);
    printf("%d", ret);
    system("pause");
    return 0;
}

当讲程序调试的时候,按F10进入调试-窗口-调用堆栈

调用main函数之前在VC6.0编辑器可以看到main函数在_tmainCRTStartup 函数中调用的,而 _tmainCRTStartup 函数是在 mainCRTStartup 被调用的。这个过程要为函数开辟栈空间, 这块栈空间我们称之为函数栈帧。

这里写图片描述
栈帧的需要ebp和esp两个寄存器。 在函数调用的过程中这两个寄存器存放了维护这个栈的栈底和栈顶指针
注意:ebp指向当前位于系统栈最上边一个栈帧的底部,而不是系统栈的底部。严格说来,“栈帧底部”和“栈底”是不同的概念;ESP所指的栈帧顶部和系统栈的顶部是同一个位置。

一.开始调用main函数:

要展开main函数的调用就得为main函数创建栈帧,转到反汇编可以看到过程:

这里写图片描述

执行上图第一条指令:
这里写图片描述
1.压栈,把ebp放入栈顶,而esp始终指向栈顶
2.将esp值传给ebp,也就是让esp,ebp移在一起
3.sub为减的意思,即将esp-0E4h赋给esp,且函数调用分配由高地址向低地址增长,因此esp向上移动,即开辟了新空间,也就是为main函数开辟空间
4.三个push压榨分别将ebx,esi,edi按顺序压入栈顶,而esp也会指向栈顶
5.lea指令,加载有效地址;将ebp-0E4h的地址放入edi中,也就是edi指向ebp-0E4h
把39h放到ecx中
把0cccccccch放到eax中
从edi所指向的地址开始向高地址进行拷贝,拷贝的次数为ecx内容,拷贝的内容为eax内

这里写图片描述
6.创建变量a与b并初始化10和20.
这里写图片描述

二.Add函数的调用

1.把b放入eax中,然后对eax压栈(形参a)
2.把a放入ecx中,然后对ecx压栈(形参b)
3.call作用:将下一条指令地址压栈,然后进入add函数里面
这里写图片描述
注意:call语句push的是下一条指令的地址,为了函数返回时知道从哪儿接着执行
接下来进入add函数:
A.先把main函数ebp压栈,保存指向main()函数栈帧底部的ebp的地址,目的是当返回时能找到main函数栈底,此时esp指向新的栈顶位置
将main函数的ebp压栈,也是为了返回时找到main函数栈底
B.将esp的值赋给ebp,产生新的ebp,即Add()函数栈帧的ebp;
C.给esp减去一个16进制数0CCh(为Add()函数预开辟空间);
D.push ebx、esi、edi;
E.lea指令,加载有效地址;
F.初始化预开辟的空间为0xcccccccc;
这里写图片描述
G.创建变量z并为其赋值
H.把形参a放到eax,即把10,放入eax把形参b加到eax中,即把20加到eax中再把eax放到z的位置,即把两数之和放到z中
I.把z的值放到寄存器eax中返回,因为z为函数临时开辟的变量空间等函数执行完会销毁,因此放寄存器中返回
K.接下来执行pop出栈操作,edi esi ebx依次从上向下出栈,esp 会向下移动,栈的特点:先进后出,后进先出
L.将ebp值赋给esp,也就是esp向下移动指向ebp位置,此时add开辟的栈空间已经销毁
M.pop将栈顶的元素弹出放到ebp中,也就是说将main函数的ebp放入ebp中,即ebp现在指向main函数ebp
这里写图片描述
N,在执行ret后,会把之前push的地址弹出去,这时就要返回main函数,这也就是为什么之前要push这个地址,这样call指令就完成了
接下来从那个call指令继续执行
这里写图片描述
O.把esp+8,即esp向下移,把形参销毁

最后就是对main函数栈帧的销毁,方法同上,就不在赘述。

栈帧的总结:

1.堆栈是C语言程序运行时必须的一个记录调用路径和参数的空间:
函数调用框架;
传递参数;
保存返回地址;
提供局部变量空间;

2. 堆栈寄存器和堆栈操作
堆栈相关的寄存器
esp,堆栈指针(stack pointer)
ebp,基址指针(base pointer)
堆栈操作
push 栈顶地址减少4个字节(32位)
pop 栈顶地址增加4个字节
ebp在C语言中用作记录当前函数调用基址

在Vue2中,可以使用watch来调用函数。具体有三种方式可以实现: 第一种方式是直接在watch中定义一个监听处理函数,当所监听的数据发生变化时,该函数会被触发。例如,在Vue实例中可以这样写: ``` watch: { cityName(newName, oldName) { // 在这执行函数的逻辑 } } ``` 第二种方式是在watch中使用字符串形式的方法名来指定要调用函数。例如,在Vue实例中可以这样写: ``` watch: { cityName: 'Namechange' } ``` 这样,当cityName的值发生变化时,会调用Vue实例中的Namechange函数。 第三种方式是使用$watch函数进行调用。$watch函数是Vue的内置方法,用于添加一个数据监听,当所监听的数据变化时,执行指定的回调函数。例如,在Vue实例中可以这样写: ``` this.$watch('cityName', function(newName, oldName) { // 在这执行函数的逻辑 }) ``` 总结起来,Vue2中watch可以通过定义监听处理函数、使用字符串形式的方法名或使用$watch函数调用函数。这些方法都可以实现在数据变化时执行相应的逻辑操作。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [vue的watch监听函数](https://blog.youkuaiyun.com/wwf1225/article/details/106590301)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [浅析vue 函数配置项watch及函数 $watch 源码分享](https://download.youkuaiyun.com/download/weixin_38663167/12949455)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值