ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

一个函数在栈上到底是怎样的

2022-05-01 11:00:48  阅读:196  来源: 互联网

标签:函数 到底 局部变量 函数调用 地址 ebp 寄存器 怎样


函数的调用和栈是分不开的,没有栈就没有函数调用,本节就来讲解函数在栈上是如何被调用的。

栈帧/活动记录

当发生函数调用时,会将函数运行需要的信息全部压入栈中,这常常被称为栈帧(Stack Frame)或活动记录(Activate Record)。活动记录一般包括以下几个方面的内容:

1) 函数的返回地址,也就是函数执行完成后从哪里开始继续执行后面的代码。例如:

int a, b, c;
func(1, 2);
c = a + b;

站在C语言的角度看,func() 函数执行完成后,会继续执行c=a+b;语句,那么返回地址就是该语句在内存中的位置。

注意:C语言代码最终会被编译为机器指令,确切地说,返回地址应该是下一条指令的地址,这里之所以说是下一条C语言语句的地址,仅仅是为了更加直观地说明问题。

2) 参数和局部变量。有些编译器,或者编译器在开启优化选项的情况下,会通过寄存器来传递参数,而不是将参数压入栈中,我们暂时不考虑这种情况。

3) 编译器自动生成的临时数据。例如,当函数返回值的长度较大(比如占用40个字节)时,会先将返回值压入栈中,然后再交给函数调用者。

当返回值的长度较小(char、int、long 等)时,不会被压入栈中,而是先将返回值放入寄存器,再传递给函数调用者。

4) 一些需要保存的寄存器,例如 ebp、ebx、esi、edi 等。之所以要保存寄存器的值,是为了在函数退出时能够恢复到函数调用之前的场景,继续执行上层函数。

下图是一个函数调用的实例:

上图是在Windows下使用VS2010 Debug模式编译时一个函数所使用的栈内存,可以发现,理论上 ebp 寄存器应该指向栈底,但在实际应用中,它却指向了old ebp。

在寄存器名字前面添加“old”,表示函数调用之前该寄存器的值。

当发生函数调用时:

  • 实参、返回地址、ebp 寄存器首先入栈;
  • 然后再分配一块内存供局部变量、返回值等使用,这块内存一般比较大,足以容纳所有数据,并且会有冗余;
  • 最后将其他寄存器的值压入栈中。

需要注意的是,不同编译器在不同编译模式下所产生的函数栈并不完全相同,例如在VS2010下选择Release模式,编译器会进行大量优化,函数栈的样貌荡然无存,不具有教学意义,所以本教程以VS2010 Debug模式为例进行分析。

关于数据的定位

由于 esp 的值会随着数据的入栈而不断变化,要想根据 esp 找到参数、局部变量等数据是比较困难的,所以在实现上是根据 ebp 来定位栈内数据的。ebp 的值是固定的,数据相对 ebp 的偏移也是固定的,ebp 的值加上偏移量就是数据的地址。例如一个函数的定义如下:

void func(int a, int b){
    float f = 28.5;
    int n = 100;
    //TODO:
}

调用形式为:

func(15, 92);

那么函数的活动记录如下图所示:

 

 这里我们假设两个局部变量挨着,并且第一个变量和 old ebp 也挨着(实际上它们之间有4个字节的空白),如此,第一个参数的地址是 ebp+12,第二个参数的地址是 ebp+8,第一个局部变量的地址是 ebp-4,第二个局部变量的地址是 ebp-8。  

标签:函数,到底,局部变量,函数调用,地址,ebp,寄存器,怎样
来源: https://www.cnblogs.com/zjuhaohaoxuexi/p/16212365.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有