x86汇编递归函数栈push/pop逻辑困惑及调用类型识别求助
x86汇编递归执行逻辑与调用约定疑问
输入为3时的递归执行流程
mov bp, sp// 使bp与sp指向栈中同一位置mov ax, [bp+2]// ax被赋值为3,该参数存储在bp上方2字节处(返回地址的下一个栈位置)cmp ax, 1// 3与1不相等jz exit// 不触发跳转,继续执行push ax// 将3压入栈(sp减2,指向bp-2位置)dec ax// ax的值变为2push ax// 将2压入栈(sp再减2,指向bp-4位置),此时栈结构如下:
返回地址 bp指向的栈位置 3 2
call func// 递归调用func函数add sp, 2// 函数返回后,调用者手动将sp加2,清理栈上的参数
上述流程会持续递归,直到ax的值变为1:此时cmp ax,1会触发jz exit,跳转到exit标签执行ret指令。
ret指令的执行逻辑与后续流程
x86的ret指令执行时会完成两个核心操作:
- 从当前栈顶弹出16位的返回地址(这个地址是当前
call func指令的下一条指令,也就是对应递归层里的add sp,2); - 将程序执行流跳转到该返回地址。
后续的回溯流程:
- 最内层递归(ax=1)执行
ret后,会跳转到上一层递归的add sp,2指令,执行该指令清理当前层压入的参数(即之前push的1); - 完成后继续执行上一层的剩余逻辑,直到触发上一层的
ret,再次弹出返回地址跳转到更外层的add sp,2; - 以此逐层回溯,直到所有递归层处理完毕,最终回到最初调用
func的位置,继续执行后续代码。
调用约定判断:_cdecl vs _stdcall
核心判断依据是栈参数的清理责任人:
_cdecl:由调用者负责清理栈参数。这段代码中,每次call func之后都有add sp,2指令,是调用者在函数返回后手动调整栈指针清理参数,完全符合_cdecl的特征。_stdcall:由被调用者负责清理栈参数,通常会使用ret n指令(比如ret 2),在返回时直接将sp加上n来清理参数,不需要调用者执行额外的add sp,2操作。
因此这段代码采用的是_cdecl调用约定。
内容的提问来源于stack exchange,提问作者nour ghazal
相关产品推荐
相关产品推荐

