1.栈帧创建
1.1栈帧
栈帧:每调用一次函数,就在栈区开辟一块独立内存区域
用来存放:返回地址,旧栈基址,局部变量,临时数据
栈整体向下生长(高地址到低地址)
两个关键寄存器:
SP 栈指针:永远指向当前栈顶
FP/BP基址指针:指向当前栈帧的起始位置(栈帧底部,用来索引 本函数局部变量)
1.2栈帧创建(调用函数时)
一共4步,底层靠push压栈+修改SP,FP硬件指令完成
1.压入返回地址
CPU执行函数跳转指令
(ARM:bl),自动把调用完这个函数之后要回去执行的那条指令地址压入栈,函数执行完靠这个地址回到原代码
2.保存旧栈帧基址FP(压栈旧FP)
把调用者函数的FP压栈保存,记录上一层函数栈帧的边界
3.更新FP=SP
让FP寄存器指向当前SP的位置,FP成为新函数栈帧的起点,后面所有局部变量都用FP做偏移来访问
4.SP继续向低地址移动(SP减小)
划出空间,存放本函数的局部变量,临时变量
一句话理解:移动SP开辟空间,FP标记栈帧起点,这片区域就是新函数的栈帧
2.函数栈帧的销毁
销毁不会清空内存数据,只修改寄存器SP,FP配合pop出栈
1.恢复SP到FP的值
SP直接赋值为当前FP,一次性回收本函数局部变量占用的全部空间,局部变量对应的内存不再属于当前栈帧
2.出栈,恢复上一层函数旧FP
从栈里取出之前保存的旧FP,还给FP寄存器,FP回到调用者函数栈帧的边界
3.出栈拿到返回地址,跳转回去
从栈顶取出预先压入的返回地址,CPU跳回调用函数的地方继续运行
❗❗栈帧销毁不会把内存里的数据清零,只是SP,FP不再指向这片内存,原来局部变量的值还留在内存,新函数调用开辟栈帧时,会直接覆盖这块内存
这就是为什莫:返回局部变量指针,拿到脏数据,产生野指针!
三.完整流程串联
mian 函数正在运行(main的栈帧)
调用func()
【栈帧创建】
1.压栈:保存返回地址(回到mian的地址)
2.压栈:保存main的FP
3.FP=SP,标记func的栈帧起点
4.SP-=N,开辟空间放func局部变量
执行func里面的代码
【栈帧销毁,return】
1.SP=FP,释放局部变量占用空间
2.POP,恢复main函数旧FP
3.POP取出返回地址,CPU跳回mian
回到main 继续执行,func栈帧作废
4.高频易错坑
1.❌栈帧销毁,系统把局部变量内存清零
✔只动SP/FP,数据残留
2.❌创建栈帧需要操作系统临时申请新内存
✔线程启动时栈整块内存就分配好了,栈帧只是在这片内存里移动SP划分空间
3..❌FP(基址指针)指向栈顶
✔FP标记当前栈帧的起始边界(栈底),用来访问局部变量
4..栈帧是每一个函数调用单独一份,递归调用就会连续创建多层栈帧,递归太深SP不断下移,栈空间耗尽,栈溢出
已完结