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
相关产品推荐
相关产品推荐

