每个方法被执行的时候,java虚拟机都会同步创建一个栈帧,栈的基本单位为栈帧,每个线程都有自已的栈,每个执行方法对应一个栈帧,也叫当前栈帧。 每一个栈帧都包括了局部变量表,操作数栈,动态连接,方法返回地址和一些额外的附加信息。 栈的特点就是后进先出,类似于坐电梯,后面进来的先出去。 特点: 局部变量的生命周期与栈帧一致:随着方法栈的销毁,局部变量随着销毁。 注: 操作数栈最大深度不会超过:max_stacks数据项中设定的最大值 byte、short和char类型在入栈前会被转成int类型; 虽然两个不同帧是相互独立的但是,为了节约一些空间,对栈进行了优化 注意:若调用方法返回的时候带了返回值,其返回会被压入当前栈帧的操作数中,并更新PC寄存器中一条需要执行的字节码指令。
栈帧是用于支持虚拟机进行方法调用和方法执行背后的数据结构。栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址信息。 操作数栈 操作数栈,也称操作栈,是一个后入先出栈。 当一个方法刚刚开始执行的时候, 该方法的操作数栈也是空的, 在方法的执行过程中, 会有各种字节码指令往操作数栈中写入和提取内容, 也就是出栈与入栈操作。 动态连接 每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用, 持有引用是为了支持方法调用过程中的动态连接(Dynamic Linking)。
单个函数调用操作所使用的栈部分被称为栈帧(stack frame)结构,其一般结构如下图所示。栈帧结构的两端由两个指针来指定。 在函数执行过程中,栈指针esp会随着数据的入栈和出栈而移动,因此函数中对大部分数据的访问都基于帧指针%ebp进行。?对于函数A调用函数B的情况,传递给B的参数包含在A的栈帧中。 当A调用B时,函数A的返回地址(调用返回后继续执行的指令地址)被压入栈中,栈中该位置也明确指明了A栈帧的结束处。而B的栈帧则从随后的栈部分开始,即图中保存帧指针(ebp)的地方开始。 这两个函数的栈帧结构如图3-5所示。可以看出,函数swap()从调用者main()的栈帧中获取其参数。图中的位置信息相对于寄存器ebp中的帧指针。栈帧左边的数字指出了相对于帧指针的地址偏移值。 前两行用来设置保存调用者的帧指针和设置本函数的栈帧指针,第5行通过把栈指针esp下移4字节为局部变量c分配空间。6~15行是swap函数的主体部分。
一、函数栈帧是什么? 在程序运行过程中,函数栈帧(Function Stack Frame) 是栈内存中为单个函数调用分配的一块独立内存区域。 其中放入乒乓球被称为压栈(push) 其中拿出乒乓球被称为出栈(pop) 1.压栈 2.出栈 3.维护栈的指针(两个寄存器) ①EBP(栈底指针):固定指向当前栈帧的 “基地址”,用于定位栈帧内的参数 ,变量b后压入栈中,此时esp栈顶指针指向下一个即将压入栈中元素的位置,ebp维护的是main函数栈帧中的基底位置。 四、总结: 函数栈帧的核心要点: ①创建栈帧:调用者压参数→压返回地址→被调用者压旧 EBP→设新 EBP→分配局部变量。 ②使用栈帧:通过 EBP 的固定偏移访问参数(EBP+偏移)和局部变量(EBP-偏移)。 ③销毁栈帧:释放局部变量→恢复旧 EBP→弹出返回地址→清理参数→回到调用者。
提示:以下是本篇文章正文内容,下面案例可供参考 一、函数栈帧 1.1函数栈帧的概念 函数栈帧是指在函数被调用时,系统为该函数在栈(Stack)区域中开辟的一段存储空间。 1.2函数栈帧的作用 函数栈帧是程序执行过程中用来进行内存管理的必备工具。当函数被调用时,系统为该函数分配栈帧空间,将函数的返回地址、帧指针、局部变量、参数等信息保存在栈帧中。 2.1减少栈帧的大小 由于函数栈帧的大小直接影响程序内存的使用效率,因此我们可以通过一些优化手段减少栈帧的大小,从而提升程序的性能。 减少栈帧的深度 由于栈帧的深度直接影响栈的大小和内存的使用效率,因此我们可以通过减少栈帧的深度来提升程序的性能。 三、函数栈帧的调试与问题排查 调试和排查函数栈帧相关的问题是在开发过程中常见的任务。
一般来讲,栈帧之间都是独立的,但是大多虚拟机都会做优化,使局部变量表和操作数栈之间有重叠,以达到共用的目的,这样能节省额外的参数复制等工作,重叠过程类似下图。 动态连接每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,Class 文件的常量池中存有大量的符号引用。字节码中的方法调用指令就以常量池中方法的符号引用为参数。 方法返回时可能会在栈帧中保存一些信息,用来恢复上层方法的执行状态。一般方法正常退出的时候,调用者的pc计数器的值可以作为返回地址,帧栈中很有可能会保存这个计数器的值作为返回地址。 方法退出的过程就是栈帧在虚拟机栈上的出栈过程,因此退出时的操作可能有:恢复上层方法的局部变量表和操作数栈,把返回值压入调用者的操作数栈每条整pc计数器的值指向调用该方法的后一条指令。 如果异常退出的话,返回地址是通过异常表来确定,栈帧中一般不会保存这部分信息。这两个出口的区别就在于,异常完成出口退出是不会给上一层调用者产生任何返回值的。
,至此main函数的栈帧保护工作完成,然后通过mov指令更新栈帧基准线,与栈顶水位线齐平。 至此红蓝两条线都恢复到了最开始的位置,main函数在栈帧恢复完成。 不准确的说,函数的栈帧就是红蓝两条线之间的内存块,它用来存放函数的临时变量,参数和返回地址。 所谓的保护栈帧恢复栈帧,不过是在保存和恢复寄存器esp和ebp的值。 至于return address是用来做:函数返回的。 随着函数的调用,函数的栈帧会逐层堆叠,但互不重合。 随着函数的逐层返回函数的栈帧会被就地放弃,但不会清理内存。 2 正括号{用来保护上层主调函数(main)的栈帧,并设置被调函数(func)的栈帧,反括号}用来放弃被调函数的栈帧,同时恢复主调函数的栈帧,这样被调函数执行完后,主调函数就能正常执行。
本篇介绍 本篇介绍下汇编中的函数,栈帧内容。 栈帧 对于intel处理器,在调用函数的时候需要保证rsp是16字节对齐的,这样设计是为了更好的支持SIMD。那体现到代码上是怎样呢? 本来在调用main函数之前rsp是16字节对齐的,可是在调用main时候,由于会将返回地址压栈,这时候rsp就不是16字节对齐了,就需要prologue中再次执行一个进栈操作,就可以保证是对齐的了。
前置知识 JVM运行时数据区 栈帧的组成 虚拟机栈 与 栈帧 虚拟机栈(JVM Stack),由 栈帧 Frame 组成。 Frame - 每个方法对应一个栈帧, 包括以下部分: Local Variable Table (局部变量表) ? return address(返回地址) a() -> b(),方法a调用了方法b, b方法的返回值放在什么地方 方法退出时会做的操作: 恢复上一个方法(调用者的栈帧)的Local Variable Table(局部变量表)和Operand Stack(操作数栈) 将返回的变量压入 上一个方法(调用者的栈帧)的Operand Stack(操作数栈) 调整 Program Counter Register (PC, 程序计数器) 的值为 当前帧的返回地址 当前栈帧弹出JVM Stack 栈, 执行Program Counter Register (PC, 程序计数器)指向的指令 理解JVM栈帧 用两个代码来帮助理解
开发环境 Ubuntu 14.04(32bits) GCC 编辑器 Cmd Markdown 画图工具 Processon 1,函数调用过程 今天先介绍下基本的函数调用过程,即栈帧。 1.1栈帧 每个函数调用都对应一个栈帧。每个栈帧由ESP和EBP寄存器来确定。每个函数执行时,其局部变量都是在自己对应的栈帧内分配内存。 -main .globl test .type test, @function test: .LFB1: .cfi_startproc pushl %ebp //ebp压栈,即old ebp ident "GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2" .section .note.GNU-stack,"",@progbits 当main函数调用test函数时,对应的栈帧见下图 当函数test返回后,main函数的栈帧如下图
其中 ESP:堆栈指针寄存器,存放执行函数对应栈帧的栈顶地址,且始终指向栈顶。 EBP:栈帧基址指针寄存器,存放执行函数对应栈帧的栈底地址,用于 C 运行库访问栈中的局部变量和参数。 0x02 栈帧 函数调用经常是嵌套的,在同一时刻,堆栈中会有多个函数的信息。每个未完成运行的函数占用一个独立的连续区域,称作栈帧 (Stack Frame)。 栈帧是堆栈的逻辑片段,当调用函数时逻辑栈帧被压入堆栈, 当函数返回时逻辑栈帧被从堆栈中弹出。栈帧存放着函数参数,局部变量及恢复前一栈帧所需要的数据等。 栈帧的边界由栈帧基地址指针 EBP 和堆栈指针 ESP 界定 (指针存放在相应寄存器中)。 EBP 指向当前栈帧底部 (高地址),在当前栈帧内位置固定;ESP 指向当前栈帧顶部 (低地址),当程序执行时 ESP 会随着数据的入栈和出栈而移动。
这些信息保存在栈帧中,并且栈帧被压入调用栈。 什么是栈帧? 栈帧是调用栈中的基本单元,每个函数调用都会在调用栈中创建一个新的栈帧。栈帧保存了函数执行所需的所有信息,包括局部变量、返回地址、参数等。 函数调用过程 当程序执行到 main 函数时,会首先在调用栈中创建一个栈帧以保存 main 函数的执行状态。然后,main 函数调用 A 函数,系统会在调用栈中为 A 函数创建一个新的栈帧。 随着 A 函数调用 B 函数,调用栈中会继续创建新的栈帧。最终,B 函数调用 C 函数,调用栈中创建了 C 函数的栈帧。 2. 调用栈示意图 为了更直观地展示上述过程,我们可以使用 UML 创建一个调用栈的示意图: 栈帧在错误处理中的应用 栈帧在错误处理和调试过程中也非常有用。 结论 程序调用栈和栈帧是理解程序执行原理的重要概念。调用栈管理函数调用的顺序,而栈帧则保存每个函数调用的详细信息。通过掌握这些概念,开发者可以更好地进行调试、错误处理和性能优化。
活动记录(Activation Record),常称栈帧(stack frame)。 ---- 嵌套过程 静态链(Static Link) 嵌套函数中,内部函数调用的栈帧可见外部函数调用的栈帧中的变量。 以frame pointer作为第一个参数(不一定是当前的栈帧,而是callee的上层)传递给callee作为static link,可以通过static link回溯上一层、上上层的栈帧,最终获得外部的变量 如果儿子1调用儿子2,那么事实上儿子1是通过父亲访问到的儿子2,因此不能直接传儿子1的栈帧,而是先回溯到父亲的栈帧,再把父亲的栈帧指针作为第一个参数传递给儿子2. F_allocLocal在栈帧上分配局部变量。
相信在学习的过程中,你对上面的问题或多或少都会有些困惑,今天的博客--函数栈帧的创建和销毁就可以帮助你解决这些困惑; 这些都是和函数的栈帧的创建和销毁有关,这个函数栈帧在不同版本的编译器有关,略有差异但是大致相同 ,经常使用的两个寄存器就是ebp和esp,这两个寄存器存放的是地址,存放的这两个地址用来维护函数的栈帧; 2.函数栈帧的初步理解 每一个函数的调用,都要在栈区开辟空间,在栈区里面,我们会优先使用高地址, 再使用低地址;我们的main函数开始执行之后,就会开辟main函数的函数栈帧,ebp esp分别指向的就是main函数的函数栈帧的边界(如图所示);我们可以把这个函数栈帧创建的过程理解为一个盖房子的过程 ,我们就会从低向高处盖房子,我们的ebp指针也被称为栈底指针,esp也被称为栈顶指针,我们现在维护的是main函数的函数栈帧,当调用其他的函数的时候,这两个指针就会维护其他的函数的栈帧空间; 我们还需要了解的就是 ,移动的距离就是0E4h,这个时候ebp和esp各自指向了新的栈底和新的栈顶,我们这个时候就完成了main函数栈帧的创建; (4)接下来反汇编里面是3个push,这三个都是进行压栈的操作,压栈完成之后的栈帧情况如下图所示
、理解栈帧 首先,什么是栈帧? 引用百度百科:C语言中,每个栈帧对应着一个未运行完的函数。栈帧中保存了该函数的返回地址和局部变量。从这句话中,可以提炼以下几点信息: 栈帧是一块因函数运行而临时开辟的空间。 每调用一次函数便会创建一个独立栈帧。 栈帧中存放的是函数中的必要信息,如局部变量、函数传参、返回值等。 当函数运行完毕栈帧将会销毁。 下面进入主题,图解函数栈帧的创建与销毁过程。 在调试过程中将转到反汇编,便能直观的看到main函数栈帧创建的过程。首先需明确的是,函数栈帧由寄存器esp,ebp维护。 进入Add()函数,可以看出这与此前main函数开辟栈帧的过程类似,说明Add()函数调用又开辟了一块独立的栈帧。
什么是函数栈帧? 理解了函数栈帧能解决什么样的问题? 函数栈帧的创建和销毁解析! 调试工具:vs 2013。 什么是函数栈帧? ebp和esp这2个寄存器中存放的是地址,这两个地址是用来维护函数栈帧的。 这就需要用到了esp和ebp,它们分别指向了main函数栈帧的两个位置,以便维护栈帧: (ebp和esp就算,调用了哪块函数,就去维护哪块函数的栈帧,此时进入的是main函数。) 根据上面所述,在调用main函数之前,esp和ebp指向的是__tmainCRTStartup这个函数的栈帧,也在维护着这个栈帧。 esp和ebp又开始正式地维护main函数的栈帧。
这些都与函数的栈帧有关系。 那么就让我们一起走进函数栈帧的创建与销毁的过程中。 3:函数栈帧的创建与销毁的解析 在解析函数栈帧的创建与销毁之前,首先呢得了解一些预备知识,这样子方便后续的理解。 esp与栈底指针ebp所维护的空间就是为main函数所开辟的函数栈帧,所对应的函数栈帧图如下。 sub前 sub后 通过过对比sub前与sub后,我们会发现此时esp和ebp不再维护main函数的函数栈帧,而是维护了一块新的栈帧,我们之前说过,函数每次调用都会在栈区上开辟一块属于自己的函数栈帧, 那么此时栈顶指针esp与栈底指针ebp所维护的空间就是为Add函数所开辟的函数栈帧,所对应的函数栈帧图如下。
一、预备知识 在正式了解函数栈帧以前,我们需要先了解一些预备知识。 大致分布如图: 我们本次所要了解的栈帧属于栈区。 2.栈帧简介 栈帧也叫过程活动记录,是编译器用来实现过程/函数调用的一种数据结构。 函数的每次调用,都有它自己独立的栈帧。 二、函数栈帧介绍 每个函数被调用时都会建立栈帧,在接下来的调试过程中我将会进一步解释。 3.函数栈帧的创建与销毁(重点) 该程序的汇编代码如下:(注释有每一步的原理) --- d:\c语言\函数栈帧hszz\函数栈帧hszz\hszz.c -------------------------
---- 1.什么是函数栈帧 函数栈帧( stack frame )就是函数调用过程中在程序的调用栈( call stack )所开辟的空间,这些空间是用来存放: 函数参数和函数返回值 3.函数栈帧的创建和销毁解析 3.1栈 栈( stack )是现代计算机程序里最为重要的概念之一,几乎每一个程序都使用了栈,没有栈就没有函数,没有局部变量,也就没有我们如今看到的所有的计算机语言。 转入目标函数 jump :通过修改 eip ,转入目标函数,进行调用 ret :恢复返回地址,压入 eip ,类似 pop eip 命令 3.3解析函数栈帧的创建和销毁 3.3.1 基本知识 1.每一次函数调用,都要为本次函数调用开辟空间,就是函数栈帧的空间。 2.这块空间的维护是使用了两个寄存器:esp ebp,ebp记录的是栈底的地址,esp记录的是栈顶的地址。 3.函数栈帧的创建和销毁过程,在不同的编译器上实现的方法大同小异。
---- 函数栈帧的创建和销毁:: ebp,esp这两个寄存器中存放的是地址,这两个地址是用来维护函数栈帧的,edp被称为栈底指针,esp被称为栈顶指针。push:压栈:给栈顶放一个元素。 pop:出栈:给栈顶删一个元素,lea:加载有效地址。dword=4byte。 答:首先为此次函数调用创建函数栈帧,在函数栈帧找空间存放局部变量值。 2.为什么局部变量的值是随机值? 随机值是系统开辟完函数栈帧后系统随机放进去的。 3.函数是怎么传参的? 返回值并不会随着函数作用域的销毁而销毁,而是放在eax中准备返回,当通过pop出栈回到main函数中再将返回值放到局部变量中。