栈和函数调用

栈是一个很重要的编程概念(编译课和程序设计课都讲过相关内容),与编译器和编程语言有紧密的联系。理解调用栈最重要的两点是:栈的结构,EBP寄 存器的作用。一个函数调用动作可分解为:零到多个PUSH指令(用于参数入栈),一个CALL指令。CALL指令内部其实还暗含了一个将返回地址(即 CALL指令下一条指令的地址)压栈的动作(由硬件完成)。几乎所有本地编译器都会在每个函数体之前插入类似如下的汇编指令:

pushl   %ebp
movl   %esp , %ebp

这样在程序执行到一个函数的实际指令前,已经有以下数据顺序入栈:参数、返回地址、ebp寄存器。由此得到类似如下的栈结构(参数入栈顺序跟调用方式有关,这里以C语言默认的CDECL为例):

+|  栈底方向         | 高位地址
  |    ...           |
  |    ...           |
  |  参数3            |
  |  参数2            |
  |  参数1            |
  |  返回地址         |
  |  上一层[ebp]    | <-------- [ebp]
  |  局部变量         |  低位地址

函数调用栈结构

这两条汇编指令的含义是:首先将ebp寄存器入栈,然后将栈顶指针esp赋值给ebp。“mov ebp esp”这条指令表面上看是用esp覆盖ebp原来的值,其实不然。因为给ebp赋值之前,原ebp值已经被压栈(位于栈顶),而新的ebp又恰恰指向栈 顶。此时ebp寄存器就已经处于一个非常重要的地位,该寄存器中存储着栈中的一个地址(原ebp入栈后的栈顶),从该地址为基准,向上(栈底方向)能获取 返回地址、参数值,向下(栈顶方向)能获取函数局部变量值,而该地址处又存储着上一层函数调用时的ebp值。

一般而言,ss:[ebp+4]处为返回地址,ss:[ebp+8]处为第一个参数值(最后一个入栈的参数值,此处假设其占用4字节内存),ss: [ebp-4]处为第一个局部变量,ss:[ebp]处为上一层ebp值。由于ebp中的地址处总是“上一层函数调用时的ebp值”,而在每一层函数调用 中,都能通过当时的ebp值“向上(栈底方向)”能获取返回地址、参数值,“向下(栈顶方向)”能获取函数局部变量值。如此形成递归,直至到达栈底。这就 是函数调用栈。

那么最开始进入main函数之前,ebp的初始值是多少呢?

关于EBP和ESP寄存器,参考这篇博客

关于栈的详细介绍,参考《Understanding the Stack》。

相关文章,《利用反汇编手段解析C语言函数》,《C函数调用机制及栈帧指针》。

时间: 2024-10-25 20:50:10

栈和函数调用的相关文章

栈中函数调用原理详解

函数调用是程序设计中的重要环节,本文就函数调用的过程进行分析. 一.eip.ebp.esp介绍 EIP,EBP,ESP都是系统的寄存器,里面存储的是些地址,我们系统中栈的实现上离不开他们三个. 我知道栈的数据结构主要特点是 后进先处.它还有两个作用: 1.栈是用来存储临时变量,函数传递的中间结果. 2.操作系统维护的,对于程序员是透明的. 下面我们就通过一个小例子说说栈的原理. 先写个小程序: 当程序进行函数调用的时候,我们经常说的是先将函数压栈,当函数调用结束后,再出栈.这一切的工作都是系统帮

程序的内存布局——函数调用栈的那点事

[注]此文是<程序员的自我修养>的读书总结,其中掺杂着一些个人的理解,若有不对,欢迎拍砖. 程序的内存布局 现代的应用程序都运行在一个虚拟内存空间里,在32位的系统里,这个内存空间拥有4GB的寻址能力.现代的应用程序可以直接使用32位的地址进行寻址,整个内存是一个统一的地址空间,用户可以使用一个32位的指针访问任意内存位置. 在进程的不同地址区间上有着不同的地位,Windows在默认情况下会将高地址的2GB空间分配给内核,而Linux默认将高地址的1GB空间分配给内核,具体的内存布局如下图:

为什么有函数调用栈?

为什么引入栈 完成函数调用的过程,需要有个地方存放函数调用返回后要执行的指令地址(简称返回地址) 极客时间-深入计算机组成原理 函数调用过程中,栈的使用 A1 call B A3 B1 B2 retq rip寄存器: 存放下一条要执行的指令地址 callq指令做两件事 把rip的地址A3压栈(也就是被调用函数返回后,调用者本来接下来会执行的那条指令的地址) // 本来这个指令地址都被放在rip了,正常情况就要执行了,但是被call指令弄走了. 把被调用函数的第一条指令地址放入rip,使得cpu下

【转】C/C++函数调用过程分析

转自:here 这里以一个简单的C语言代码为例,来分析函数调用过程 代码: #include <stdio.h> int func(int param1 ,int param2,int param3) { int var1 = param1; int var2 = param2; int var3 = param3; printf("var1=%d,var2=%d,var3=%d",var1,var2,var3); return var1; } int main(int a

栈,堆,静态存储区

1.栈是先入后出, 2.栈保存了一个函数调用所需的维护信息,函数参数,函数返回地址,局部变量,函数调用上下文.没有栈就没有函数,没有局部变量. 3. 栈保存了一个函数调用所需的维护信息(函数参数,函数返回值地址,局部变量,函数调用上下文) 4.栈上的数据在函数返回后就会被释放掉,无法传递到函数外部 5.堆是程序中一块巨大的内存空间,可由程序自由使用 6.堆中被程序申请使用的内存在程序主动释放前将一直有效 7.堆空间通过申请才能获得 8.系统对堆空间的管理方式(空闲链表法,位图法,对象池法等等)

栈与调用惯例

先介绍一下程序的内存布局 现代的应用程序都运行在一个内存空间里,在32位的系统中,内存大小为4GB(2的32次方),整个内存是一个统一的地址空间,用户可以用一个32位的指针访问内存的任意位置. 但其实大多数操作系统会把4GB的内存空间中的一部分分给内核使用,被称为内核空间,应用程序无法直接访问.Windows下会默认把高2GB的空间分配给内核(可配置为1GB),Linux下默认将高地址的1GB空间分配给内核.剩下的空间称为用户地址空间. 用户地址空间中还有一个保留区和动态链接库映射区 栈 栈是一

栈,堆,全局,文字常量,代码区总结

林炳文Evankaka原创作品.转载请注明出处http://blog.csdn.net/evankaka        在C\C++中,通常可以把内存理解为4个分区:栈.堆.全局/静态存储区和常量存储区.下面我们分别简单地介绍一下各自的特点. 一.   区域划分 堆: 是大家共有的空间,分全局堆和局部堆.全局堆就是所有没有分配的空间,局部堆就是用户分配的空间.堆在操作系统对进程初始化的时候分配,运行过程中也可以向系统要额外的堆,但是记得用完了要还给操作系统,要不然就是内存泄漏. 栈:是个线程独有

基本数据结构 -- 栈详解

栈是一种后进先出的线性表,是最基本的一种数据结构,在许多地方都有应用. 一.什么是栈 栈是限制插入和删除只能在一个位置上进行的线性表.其中,允许插入和删除的一端位于表的末端,叫做栈顶(top),不允许插入和删除的另一端叫做栈底(bottom).对栈的基本操作有 PUSH(压栈)和 POP (出栈),前者相当于表的插入操作(向栈顶插入一个元素),后者则是删除操作(删除一个栈顶元素).栈是一种后进先出(LIFO)的数据结构,最先被删除的是最近压栈的元素.栈就像是一个箱子,往里面放入一个小盒子就相当于

理解 &quot;栈&quot; &quot;队列&quot;,&quot;堆&quot;(后进先出)

[栈] ??函数调用形成了一个栈帧 function foo(b) { var a = 10; return a + b + 11; } function bar(x) { var y = 3; return foo(x * y); } console.log(bar(7)); 当调用bar时,创建了第一个帧 ,帧中包含了bar的参数和局部变量.当bar调用foo时,第二个帧就被创建,并被压到第一个帧之上,帧中包含了foo的参数和局部变量.当foo返回时,最上层的帧就被弹出栈(剩下bar函数的调