汇编指令push,mov,call,pop,leave,ret建立与释放栈的过程 |
您所在的位置:网站首页 › 汇编语言中mov是什么意思 › 汇编指令push,mov,call,pop,leave,ret建立与释放栈的过程 |
栈内的数据
栈在汇编层面是辅助实现函数调用的,每个函数调用过程在栈中被抽象成一帧 ,在老式的32位CPU架构IA32中还有固定寄存器指向当前帧底部(下图中的0x100000f4,0x100000d8)。每帧内保存的数据为当前函数的局部变量,当前函数内调用其他函数的参数,寄存器状态(调用者与被调用者依据约定分别负责保存与恢复部分寄存器),上一帧底部地址(x86-64取消),当前函数返回后的指令地址,各组别数据的长度不定顺序固定。 高级语言中的栈结构有数据后进先出的性质,永远都是操作栈最顶层的数据。在汇编指令层面有一个**%rsp寄存器总是保存着栈顶的内存地址**,以此抽象出一个栈指针指向栈顶数据,利用汇编指令push与pop完成数据的入栈与出栈,push与pop负责将%rsp保存的内存地址中的数据移到目标地址,并对%rsp内的地址进行加减操作。 push 源数据 //push指令可以分解为两条更基本的汇编指令: //--sub $指针移动长度 %rsp //栈指下移 //--mov 源数据 (%rsp) //推入数据至栈顶 pop 目标地址 //pop指令等同于: //--mov (%rsp)目标地址 //数据出栈 //--add $指针移动长度 %rsp //指针上移
调用一个函数不一定100%会对栈进行分配,出于速度与性能cpu会优先考虑只用寄存器组完成函数的运行,但如果涉及下面的情况则一般会进行栈的分配: 1,寄存器组无法储存所有的函数局部变量。(当前函数或与它调用的函数累计) 2,有些局部变量是数组或结构。 3,需要计算局部变量的内存地址。 4,有调用另一个函数且参数超过6个。 举例,假如有函数声明和调用如下: func1(a,b) ... func2(2,2) ... return end func2(a,b) ... return end main() func1(1,1) end以上代码在一个老式的IA32的栈中的分配时机大概图示:
call指令会完成以下3个动作,1,更改PC(%rip寄存器,负责指向下一条指令的地址)为func1函数的第一条相关指令的地址。2,将栈指针向下移动8Bytes(x64)。3,向栈中推入指令call func1的下一条指令的内存地址。 如上文所说,push指令等同于subq+moveq,它们三个的使用与顺序比较灵活,编译器可自行决定。 在代码中,当一个函数return或所有代码行运行结束则会跳回调用该函数的下一行代码处,所有局部变量都会释放。在汇编层面(32位指令架构)中由以下三条指令实现: mov 帧指针内保存的帧底地址 %rsp //栈指针上移动到帧指针处 pop 帧指针寄存器 //将上一帧帧底地址出栈 ret //将指令返回地址出栈等同于: leave ret在x86-64中由于取消了帧指针,可以更简洁的化为以下两条指令: add %rsp 偏移值 //栈指针上移到上一帧帧顶 ret也等同于 leave retret相当于pop %rip,将返回地址出栈到程序计数器。leave指令相当于对mov,pop(IA32)或add(x86-64)包了一层(除了可恢复栈指针外好像还可以恢复被调用者寄存器状态)。总而言之,编译器对运行时栈的分配与回收就是依靠栈帧指针的上下移动来完成(x86-64只依赖栈指针)。 参考: 深入理解计算机系统 第二版,第三版 R.E.Bryant 维护日志: 2020-1-12:重写 |
今日新闻 |
推荐新闻 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |