将C代码转为MIPS汇编时出现算术溢出异常的求助
首先明确你的场景:你要把这段C代码转换为MIPS汇编:
int call(int arg) { return foo(arg+10) + bar(arg) + baz(one(), two(arg-1), arg); }
你的MIPS实现运行时在add $s0, $s0, $v0这一行触发了算术溢出异常,下面是具体的原因分析和解决办法:
一、溢出异常的直接原因
MIPS的add指令是带符号溢出检查的加法指令。当你用它计算两个有符号整数的和时,如果结果超出了32位有符号整数的取值范围(即大于0x7FFFFFFF或小于0x80000000),CPU就会触发算术溢出异常。
你这行代码是在累加foo(...) + bar(...) + baz(...)的结果,当这三个函数返回值的总和超出32位有符号整数范围时,add指令就会抛出这个异常。
二、额外的代码规范问题(影响稳定性)
除了溢出问题,你的代码还有一个不符合MIPS ABI(应用二进制接口)的问题:
你直接使用了$s0和$s1这两个保存寄存器(callee-saved registers),但没有在函数开头将它们的值保存到栈上,也没有在函数返回前恢复。根据MIPS的调用约定,这类寄存器的值需要被函数保留,如果你直接修改它们,会破坏调用者的寄存器上下文,可能导致其他不可预期的错误。
三、具体的修复方案
方案1:替换为无溢出检查的加法指令
如果你不需要对溢出进行检查(比如业务逻辑允许结果溢出后按二进制回绕,或者你处理的是无符号整数),可以把所有的add指令替换为addu(无溢出检查的加法):
# 替换原来的add指令 addu $s0, $v0, $zero # s0 = foo(arg+10) addu $s0, $s0, $v0 # s0 = foo(...) + bar(...) addu $s0, $s0, $v0 # s0 = 三个函数返回值的总和 addu $v0, $s0, $zero
addu指令不会检查溢出,即使结果超出有符号范围,也只会按二进制位的方式计算结果,不会触发异常。
方案2:处理溢出(如果需要确保结果合法)
如果你需要保证结果在有符号整数范围内,那么需要在加法前手动检查溢出情况:
比如,在执行add之前,检查两个操作数的符号:
- 如果两个正数相加结果为负数,或者两个负数相加结果为正数,就说明会溢出,此时可以加入对应的错误处理逻辑(比如设置错误码、终止程序等)。
方案3:修复寄存器保存问题
按照MIPS ABI规范,使用$s0和$s1前需要先保存它们的值到栈上,函数结束时恢复:
.globl callMany callMany: # 保存$s0、$s1到栈上(因为我们要修改它们) addiu $sp, $sp, -12 sw $s0, 0($sp) sw $s1, 4($sp) # 原业务逻辑 add $s1, $a0,$zero addi $a0, $a0, 10 jal foo addu $s0, $v0, $zero #s0 = foo(arg+10) add $a0, $s1, $zero jal bar addu $s0, $s0, $v0 #s0 = foo(arg+10)+bar(arg) addiu $sp, $sp, -8 jal one sw $v0, 0($sp) #save one() addi $a0, $s1, -1 jal two sw $v0, 4($sp) #save two(arg-1) lw $a0, 0($sp) lw $a1, 4($sp) addiu $sp, $sp, 8 add $a2, $s1, $zero jal baz addu $s0, $s0, $v0 # 用addu避免溢出异常 addu $v0, $s0, $zero # 恢复$s0、$s1 lw $s1, 4($sp) lw $s0, 0($sp) addiu $sp, $sp, 12 jr $ra
内容的提问来源于stack exchange,提问作者Jinfan Hu

