每个方法被执行的时候,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减少栈帧的大小 由于函数栈帧的大小直接影响程序内存的使用效率,因此我们可以通过一些优化手段减少栈帧的大小,从而提升程序的性能。 减少栈帧的深度 由于栈帧的深度直接影响栈的大小和内存的使用效率,因此我们可以通过减少栈帧的深度来提升程序的性能。 三、函数栈帧的调试与问题排查 调试和排查函数栈帧相关的问题是在开发过程中常见的任务。
,至此main函数的栈帧保护工作完成,然后通过mov指令更新栈帧基准线,与栈顶水位线齐平。 至此红蓝两条线都恢复到了最开始的位置,main函数在栈帧恢复完成。 不准确的说,函数的栈帧就是红蓝两条线之间的内存块,它用来存放函数的临时变量,参数和返回地址。 所谓的保护栈帧恢复栈帧,不过是在保存和恢复寄存器esp和ebp的值。 至于return address是用来做:函数返回的。 随着函数的调用,函数的栈帧会逐层堆叠,但互不重合。 随着函数的逐层返回函数的栈帧会被就地放弃,但不会清理内存。 2 正括号{用来保护上层主调函数(main)的栈帧,并设置被调函数(func)的栈帧,反括号}用来放弃被调函数的栈帧,同时恢复主调函数的栈帧,这样被调函数执行完后,主调函数就能正常执行。
一般来讲,栈帧之间都是独立的,但是大多虚拟机都会做优化,使局部变量表和操作数栈之间有重叠,以达到共用的目的,这样能节省额外的参数复制等工作,重叠过程类似下图。 动态连接每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,Class 文件的常量池中存有大量的符号引用。字节码中的方法调用指令就以常量池中方法的符号引用为参数。 方法返回时可能会在栈帧中保存一些信息,用来恢复上层方法的执行状态。一般方法正常退出的时候,调用者的pc计数器的值可以作为返回地址,帧栈中很有可能会保存这个计数器的值作为返回地址。 方法退出的过程就是栈帧在虚拟机栈上的出栈过程,因此退出时的操作可能有:恢复上层方法的局部变量表和操作数栈,把返回值压入调用者的操作数栈每条整pc计数器的值指向调用该方法的后一条指令。 如果异常退出的话,返回地址是通过异常表来确定,栈帧中一般不会保存这部分信息。这两个出口的区别就在于,异常完成出口退出是不会给上一层调用者产生任何返回值的。
其中 ESP:堆栈指针寄存器,存放执行函数对应栈帧的栈顶地址,且始终指向栈顶。 EBP:栈帧基址指针寄存器,存放执行函数对应栈帧的栈底地址,用于 C 运行库访问栈中的局部变量和参数。 0x02 栈帧 函数调用经常是嵌套的,在同一时刻,堆栈中会有多个函数的信息。每个未完成运行的函数占用一个独立的连续区域,称作栈帧 (Stack Frame)。 栈帧是堆栈的逻辑片段,当调用函数时逻辑栈帧被压入堆栈, 当函数返回时逻辑栈帧被从堆栈中弹出。栈帧存放着函数参数,局部变量及恢复前一栈帧所需要的数据等。 栈帧的边界由栈帧基地址指针 EBP 和堆栈指针 ESP 界定 (指针存放在相应寄存器中)。 EBP 指向当前栈帧底部 (高地址),在当前栈帧内位置固定;ESP 指向当前栈帧顶部 (低地址),当程序执行时 ESP 会随着数据的入栈和出栈而移动。
开发环境 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函数的栈帧如下图
本篇介绍 本篇介绍下汇编中的函数,栈帧内容。 栈帧 对于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栈帧 用两个代码来帮助理解
三、修改环境 现在用户已经知道了系统启动文件的位置和内容,就可以修改启动文件,来自定义我们的环境。(准) 1.用户应当修改哪些文件 一般来说,在 PATH 中添加目录或定义额外的环境变量,需要将这些更改放入到 .bash_profile 文件中(或者是其它的等效文件,这取决于系统的发行版本,比如 Ubuntu 系统使用的是 .profile 文件),其它的改变则应录入 .bashrc 文件中。除非是系统管理员需要修改用户公用的默认设置,普通用户只需对主目录下的文件作出修改即可。当然用户也可以修改其它目录
这些信息保存在栈帧中,并且栈帧被压入调用栈。 什么是栈帧? 栈帧是调用栈中的基本单元,每个函数调用都会在调用栈中创建一个新的栈帧。栈帧保存了函数执行所需的所有信息,包括局部变量、返回地址、参数等。 函数调用过程 当程序执行到 main 函数时,会首先在调用栈中创建一个栈帧以保存 main 函数的执行状态。然后,main 函数调用 A 函数,系统会在调用栈中为 A 函数创建一个新的栈帧。 随着 A 函数调用 B 函数,调用栈中会继续创建新的栈帧。最终,B 函数调用 C 函数,调用栈中创建了 C 函数的栈帧。 2. 调用栈示意图 为了更直观地展示上述过程,我们可以使用 UML 创建一个调用栈的示意图: 栈帧在错误处理中的应用 栈帧在错误处理和调试过程中也非常有用。 结论 程序调用栈和栈帧是理解程序执行原理的重要概念。调用栈管理函数调用的顺序,而栈帧则保存每个函数调用的详细信息。通过掌握这些概念,开发者可以更好地进行调试、错误处理和性能优化。
---- 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.函数栈帧的创建和销毁过程,在不同的编译器上实现的方法大同小异。
那么通过学习函数栈帧的创建和销毁,以上困惑就会迎刃而解。 注: 本次讲解使用的是vs2013,不要使用太高级的编译器,越高级的编译器,越不容易学习和观察;同时,在不同的编译器下,函数调用过程中栈帧的创建是略有差异的,具体细节取决于编译器的实现。 为了讲清楚函数栈帧,我们需要先做一些铺垫: 寄存器: eax ebx ecx edx ebp esp ebp、esp这2个寄存器中存放的是地址,这2个地址是用来维护函数栈帧的 每一个函数调用,都要在栈区创建一个空间 接下来,就正式开始介绍函数栈帧的创建和销毁 push ebp mov ebp,esp sub esp,0E4h push ebx push esi push 总结: 局部变量在函数的栈帧里被分配了一些空间进行创建 局部变量不初始化的时候是随机值(比如上述过程中不初始化之前是cccccccc) 函数在调用之前就把参数从右向左进行压栈;真正进入函数后通过指针的偏移量找到形参
提示:以下是本篇文章正文内容,下面案例可供参考 一、函数栈帧的创建 函数栈帧的创建是在函数调用时进行的,栈帧中包含了局部变量、函数参数、返回地址和调用者的上下文等信息。具体的创建过程如下: 1. 函数参数的传递 在函数调用时,参数的值会被压入栈中,这些参数会成为新栈帧的一部分。在栈帧中,函数参数的位置是从高地址到低地址分配的。 2. 二、函数栈帧的销毁 函数栈帧的销毁是在函数返回时进行的。在函数返回之前,需要将栈帧中的信息恢复并将其从栈中弹出。具体的销毁过程如下: 1. 减小栈指针 在完成上述过程后,需要将栈指针(P)向下移动,以便将当前栈帧从栈中弹出。当栈指针恢复到上一个栈帧的位置时,程序会从该处继续执行,直到遇到下一个函数调用。 栈帧的大小限制: 栈帧的大小是由局部变量、函数参数和其他信息所占用的内存大小决定的。在设计函数时,我们应该合理估计局部变量的大小和数量,避免栈帧过大导致栈溢出。
---- 函数栈帧的创建和销毁:: ebp,esp这两个寄存器中存放的是地址,这两个地址是用来维护函数栈帧的,edp被称为栈底指针,esp被称为栈顶指针。push:压栈:给栈顶放一个元素。 pop:出栈:给栈顶删一个元素,lea:加载有效地址。dword=4byte。 答:首先为此次函数调用创建函数栈帧,在函数栈帧找空间存放局部变量值。 2.为什么局部变量的值是随机值? 随机值是系统开辟完函数栈帧后系统随机放进去的。 3.函数是怎么传参的? 返回值并不会随着函数作用域的销毁而销毁,而是放在eax中准备返回,当通过pop出栈回到main函数中再将返回值放到局部变量中。
函数栈帧的创建和销毁 函数栈帧的介绍 函数栈帧是计算机程序运行时在调用栈(Call Stack)上为每个函数调用分配的一块内存区域,用于存储该函数执行所需的各种信息。 栈帧的关键寄存器 在x86架构中,有两个关键寄存器与栈帧相关: ESP (Extended Stack Pointer):指向栈顶(当前可用的最低地址) EBP (Extended Base Pointer ):指向当前栈帧的基址 VS中的调用堆栈 栈帧的基本结构 一个典型的函数栈帧包含以下几个主要部分(从高地址向低地址生长): 函数参数:调用者传递给被调用函数的参数 返回地址:函数执行完毕后应该返回的指令地址 前一个栈帧的基址(EBP/RBP):保存调用者的栈帧基址 局部变量:函数内部定义的变量 临时空间:用于表达式计算等临时存储 栈帧的重要性 实现函数调用和返回:保证程序能正确地在函数间跳转和返回 隔离函数状态:每个函数有自己的栈帧,互不干扰 支持递归调用:每次递归调用都会创建新的栈帧 调试信息:栈帧包含调试器回溯调用链所需的信息 作者寄语 对于函数栈帧,我们只需了解一些运行原理即可
活动记录(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,这三个都是进行压栈的操作,压栈完成之后的栈帧情况如下图所示