一、栈
栈是常见的数据结构,在发生程序调用的情况下,操作系统会为被调用的子程序开辟一块栈空间。栈除了先入后出(FILO)外还有以下特性:
每一个进程在用户态对应一个调用栈结构(call stack) 程序中每一个未完成运行的函数对应一个栈帧(stack frame),栈帧中保存函数局部变量、传递给被调函数的参数等信息 栈底对应高地址,栈顶对应低地址,栈由内存高地址向低地址生长
二、寄存器
寄存器位于CPU内部,用于存放程序执行中用到的数据和指令,CPU从寄存器中取数据比从内存中取快。对于有特定用途的几个寄存器,简要介绍如下:
ax(accumulator): 可用于存放函数返回值 EBP(extended base pointer): 存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈底 ESP(extended stack poinger): 存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶 ip(instruction pointer): 指向当前执行指令的下一条指令
如上图所示,以 i386 为例,栈底地址为0x0A,存储在 EBP 寄存器中, 栈顶地址为0x00,保存在 ESP 寄存器中。如果执行 PUSH 指令,向栈上压入数据会导致 ESP 的值减小,执行 POP 指令,从栈里弹出数据则会使得 ESP 增大。
三、函数调用过程
函数调用步骤为:
参数入栈:将函数的参数从右向左依次压入系统栈中 返回地址入栈:将当前代码区调用指令的下一条指令地址压入栈中,供函数返回时继续执行 代码区跳转:处理器从当前代码区跳转到被调用函数的入口处 栈帧调整:a、保存当前栈帧状态值,已备后面恢复本栈帧时使用(EBP入栈) b、将当前栈帧切换到新栈帧。(将ESP值装入EBP,更新栈帧底部) c、给新栈帧分配空间。(把ESP减去所需空间的大小,抬高栈顶)
假设代码运行的顺序为mian函数调用fun_A,然后fun_A调用fun_B,为了方便理解栈帧变化状态,在下图中的栈底位于最下方。
函数执行过程为:
在main函数调用func_A的时候,首先在自己的栈帧中压入函数返回地址,然后为func_A创建新栈帧并压入系统栈在func_A 调用func_B的时候,同样先在自己的栈帧中压入函数返回地址,然后为func_B创建新栈帧并压入系统栈 在func_B返回时,func_B的栈帧被弹出系统栈,func_A栈帧中的返回地址被“露”在栈顶,此时处理器按照这个返回地址重新跳到func_A代码区中执行 在func_A返回时,func_A的栈帧被弹出系统栈,main函数栈帧中的返回地址被“露”在栈顶,此时处理器按照这个返回地址跳到main函数代码区中执行
参考资料:
https://www.zhihu.com/question/22444939/answer/22200552 https://zhuanlan.zhihu.com/p/24291978?refer=hinus https://zhuanlan.zhihu.com/p/25149493
https://blog.csdn.net/boer521314/article/details/41411593