美文网首页
一个函数在栈上到底是怎样的?

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

作者: andy_shx | 来源:发表于2020-06-11 20:32 被阅读0次

转载,详见原文:https://www.cnblogs.com/zhangjinfu/articles/11276542.html
函数的调用和栈是分不开的,没有栈就没有函数调用,本节就来讲解函数在栈上是如何被调用的。

栈帧/活动记录

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

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

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">1 int a, b, c; 2 func(1, 2); 3 c = a + b;</pre>

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

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

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

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

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

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

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

image

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

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

当发生函数调用时:

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

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

关于数据的定位

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

例如一个函数的定义如下:

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">1 void func(int a, int b){ 2 float f = 28.5; 3 int n = 100; 4 //TODO:
5 }</pre>

调用形式为:

<pre class="info-box" style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; color: rgb(51, 51, 51); font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;">func(15, 92);</pre>

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

image

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

相关文章

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

    转载,详见原文:https://www.cnblogs.com/zhangjinfu/articles/11276...

  • 巧用函数栈实现栈的反转

    一、函数栈 函数的调用过程其实就是一个压栈的过程,在函数栈中,每个函数所占空间成为一个 栈帧。栈帧中保存着函数的形...

  • 内存的五大区域

    栈 在函数体中定义的变量通常是在栈上 堆 用malloc, calloc, realloc等分配内存的函数分配得到...

  • Block使用注意事项

    属性 block是一个函数对象,在程序运行中生成。在运行时,block创建在栈(stack)上,和其他分配在栈上的...

  • ios中Block使用

    属性block是一个函数对象,在程序运行中生成。在运行时,block创建在栈(stack)上,和其他分配在栈上的对...

  • Python 函数的执行流程-函数递归-匿名函数-生成器

    1 函数的执行流程 函数的执行需要对函数进行压栈的,什么是压栈呢,简而言之就是在函数执行时在栈中创建栈帧存放需要变...

  • 面试题30. 包含min函数的栈

    包含min函数的栈 题目描述 定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中,...

  • 函数调用栈平衡

    栈平衡 栈平衡:函数调用前后的栈顶指针指向的位置不变 内平栈 外平栈 内平栈: 指的是在函数调用返回之前使栈保持...

  • 《软件调试》

    [P18]目前的主流CPU架构都是使用栈来进行函数调用的,栈上记录了函数的返回地址,因此,通过递归是寻找放在栈上的...

  • 数组

    一. 栈和堆 栈:当一个函数调用时,会在栈内存中申请一个空间,当函数中定义一个变量时,会分配到这个函数申请的栈空间...

网友评论

      本文标题:一个函数在栈上到底是怎样的?

      本文链接:https://www.haomeiwen.com/subject/ndeitktx.html