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

x86汇编递归函数序言导致参数异常问题求助

递归背包算法的栈帧参数错位问题:诊断与修复

嘿,这是x86汇编调用约定和栈管理的经典坑!你的核心问题是参数压栈顺序完全颠倒,再加上调用后没清理栈参数,导致递归时栈帧彻底混乱,参数被错误覆盖。下面一步步给你拆解问题,再附上修复后的完整代码:

1. 最致命的错误:违背CDECL调用约定的参数顺序

在x86的CDECL调用规范里,函数参数必须从右往左压入栈。你的函数签名是:

int knapsack(int* w, int* v, int num_items, int capacity, int current);

也就是说,调用时要先压最后一个参数current,然后是capacity、num_items、v,最后才是第一个参数w。但你现在的代码是从左往右压栈,直接导致递归调用时所有参数的位置完全错位——这就是你看到capacity被替换成数组指针的根本原因!

2. 容易忽略的小问题:数组索引越界

在maximizeItemUsage里计算当前物品权重时,你用了num_items作为索引:

movl num_items(%ebp), %edx # num_items
imul $4, %edx # 4(num_items)
addl %edx, %ecx # m_w + 4(num_items)

但数组是0起始的,当前最后一个物品的索引应该是num_items-1,用num_items会访问数组外的内存,拿到错误的权重值,进一步干扰递归逻辑。

3. 遗漏的栈清理步骤

CDECL约定要求调用者负责清理栈上的参数。每次call knapsack之后,你需要把栈指针加上20(5个参数×4字节),否则栈会残留之前的参数,后续的栈帧结构会彻底乱掉。

修复后的完整代码

# Function signature:
# int knapsack(int* w, int* v, int num_items,
# int capacity, int current);
.text
# Define our macros, which store the location of the parameters and values
# relative to the stack's base pointer (EBP)
.equ weights, 8
.equ values, 12
.equ num_items, 16
.equ capacity, 20
.equ cur_value, 24
.equ do_not_use, -4
.equ use, -8
.global knapsack
knapsack:
# solves the knapsack problem
# @weights: an array containing how much each item weighs
# @values: an array containing the value of each item
# @num_items: how many items that we have
# @capacity: the maximum amount of weight that can be carried
# @cur_weight: the current weight
# @cur_value: the current value of the items in the pack
# Prologue (prepare the stack frame)
push %ebp
mov %esp, %ebp
# Make space for local variables on the stack
# 2 variables (4 bytes each)
sub $8, %esp
movl $0, do_not_use(%ebp)
movl $0, use(%ebp)
# Base Case: we have utilized all the items or there is no more space left
# in the bag (num_items = 0 or capacity = 0)
cmpl $0, num_items(%ebp)
jle emptyBag
cmpl $0, capacity(%ebp)
jle emptyBag
# Case 1: We do not use the current element because adding it will surpass capacity
# weights[n - 1] > capacity
# Compute weights[n - 1] (stored in ECX register)
movl weights(%ebp), %ecx
push %edx
movl num_items(%ebp), %edx # num_items
decl %edx # num_items - 1
imul $4, %edx # 4*(num_items - 1)
addl %edx, %ecx # weights + 4*(num_items - 1)
movl (%ecx), %ecx # Get value of weights[num_items-1]
pop %edx
# If weights[n-1] > capacity, jump to analyzePreviousItems
cmpl %ecx, capacity(%ebp)
jl analyzePreviousItems
# Case 2: We can use the current element
jmp maximizeItemUsage

emptyBag:
movl $0, %eax
# Epilogue
mov %ebp, %esp
pop %ebp
ret

maximizeItemUsage:
# --------------------------
# Recursive call: do NOT use current item
# knapsack(weights, values, num_items-1, capacity, cur_value)
# --------------------------
# 正确的压栈顺序:从右往左压参数
push cur_value(%ebp)       # 第5个参数:current
push capacity(%ebp)        # 第4个参数:capacity
movl num_items(%ebp), %edx
decl %edx
push %edx                  # 第3个参数:num_items-1
push values(%ebp)          # 第2个参数:values
push weights(%ebp)         # 第1个参数:weights
call knapsack
add $20, %esp              # 清理栈上的5个参数(5*4=20字节)
movl %eax, do_not_use(%ebp)

# --------------------------
# Recursive call: USE current item
# knapsack(weights, values, num_items-1, capacity - weights[num_items-1], cur_value)
# --------------------------
# 计算 capacity - weights[num_items-1]
movl weights(%ebp), %ecx
push %edx
movl num_items(%ebp), %edx
decl %edx
imul $4, %edx
addl %edx, %ecx
movl (%ecx), %ecx          # weights[num_items-1]
pop %edx
movl capacity(%ebp), %edx
subl %ecx, %edx            # capacity - weights[num_items-1]

# 正确压栈参数(从右往左)
push cur_value(%ebp)       # 第5个参数:current
push %edx                  # 第4个参数:capacity - weights[num_items-1]
movl num_items(%ebp), %edx
decl %edx
push %edx                  # 第3个参数:num_items-1
push values(%ebp)          # 第2个参数:values
push weights(%ebp)         # 第1个参数:weights
call knapsack
add $20, %esp              # 清理栈参数

# 加上当前物品的价值(你之前漏掉了这一步!)
movl %eax, %ecx
movl values(%ebp), %edx
movl num_items(%ebp), %eax
decl %eax
imul $4, %eax
addl %eax, %edx
addl (%edx), %ecx          # ecx = knapsack(...) + values[num_items-1]
movl %ecx, use(%ebp)

# 比较两个选项,取最大值
movl do_not_use(%ebp), %eax
cmpl %eax, use(%ebp)
jle skip_set_max
movl use(%ebp), %eax
skip_set_max:
# Epilogue
mov %ebp, %esp
pop %ebp
ret

analyzePreviousItems:
# --------------------------
# Recursive call: do NOT use current item
# knapsack(weights, values, num_items-1, capacity, cur_value)
# --------------------------
push cur_value(%ebp)       # 第5个参数
push capacity(%ebp)        # 第4个参数
movl num_items(%ebp), %edx
decl %edx
push %edx                  # 第3个参数
push values(%ebp)          # 第2个参数
push weights(%ebp)         # 第1个参数
call knapsack
add $20, %esp              # 清理栈参数
movl %eax, do_not_use(%ebp)

movl do_not_use(%ebp), %eax
# Epilogue
mov %ebp, %esp
pop %ebp
ret

额外说明

  • 所有递归调用的参数压栈顺序都修正为CDECL要求的从右往左,确保栈帧参数对齐正确
  • 修复了数组索引越界问题,正确访问当前物品的权重和价值
  • 每次调用函数后都添加了add $20, %esp清理栈参数,避免栈溢出和帧混乱
  • 补充了maximizeItemUsage中“使用当前物品”后的价值计算——你之前的代码只递归调用了,但忘了加上当前物品的价值,这会导致结果错误!

内容的提问来源于stack exchange,提问作者Adam Lee

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.11 07:34:36