You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

x86汇编递归函数栈push/pop逻辑困惑及调用类型识别求助

x86汇编递归执行逻辑与调用约定疑问

输入为3时的递归执行流程

  1. mov bp, sp // 使bp与sp指向栈中同一位置
  2. mov ax, [bp+2] // ax被赋值为3,该参数存储在bp上方2字节处(返回地址的下一个栈位置)
  3. cmp ax, 1 // 3与1不相等
  4. jz exit // 不触发跳转,继续执行
  5. push ax // 将3压入栈(sp减2,指向bp-2位置)
  6. dec ax // ax的值变为2
  7. push ax // 将2压入栈(sp再减2,指向bp-4位置),此时栈结构如下:
返回地址
bp指向的栈位置
3
2
  1. call func // 递归调用func函数
  2. add sp, 2 // 函数返回后,调用者手动将sp加2,清理栈上的参数

上述流程会持续递归,直到ax的值变为1:此时cmp ax,1会触发jz exit,跳转到exit标签执行ret指令。


ret指令的执行逻辑与后续流程

x86的ret指令执行时会完成两个核心操作:

  1. 从当前栈顶弹出16位的返回地址(这个地址是当前call func指令的下一条指令,也就是对应递归层里的add sp,2);
  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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 13:45:27