那么我为什么要使用 addi 而不是 addiu 呢?
通常你不应该,除非assert()在加法中没有 2 的补码符号溢出。 您可能希望在您签署了您希望不会溢出的值的情况下这样做,尤其是在手工编写 asm 时。(编译器从不使用add,总是addu。)
除此之外,这些指令实际上是相同的,包括 16 位立即数的符号扩展addiu。(与ori零扩展的其他布尔值不同。MIPS 确实需要对某些指令进行零扩展,但符号扩展addiu意味着它也不需要subiu操作码来减去小整数。)
在您自己使用的程序中,让您的程序因错误的输入而出错可能比让一些不应该包装的东西更好。如果您正在为 Linux MIPS 而不是 MARS 编写代码,则可以为 SIGFPE(算术异常)安装信号处理程序,这样您至少可以打印一条人性化的错误消息,例如“检测到有符号溢出,正在中止”。在 MARS 中,我假设您只是进入调试器。
存在的唯一原因addand addi(而不仅仅是正常的adduand addiu,它执行大多数 ISA (如 x86 和 ARM 调用add))是整数溢出检测。
整数溢出检测是一个难题。(另见https://en.wikipedia.org/wiki/Integer_overflow)。ISA 已经进行了各种尝试来为它提供硬件支持,让软件(尤其是高级编译语言)经常使用它。MIPS 的add/sub指令就是这样一种尝试,但不包括左移等情况。因此,想要对每个操作提供溢出检查的高级语言将需要一个单独的机制。或者他们可能只使用其他机制而不add/根本不使用sub。
与 MIPS 相比,考虑像 x86 或 ARM 这样的 ISA:它们有一个带有符号溢出位的 FLAGS 或状态寄存器,并且如果最后一条指令设置了该位,则可以进行条件分支。x86 甚至有一个into(如果在 FLAGS 中设置了 OF 则陷阱)指令。
但是 MIPS 根本没有标志寄存器,只有整数寄存器。所以没有地方可以记录加法溢出的事实,除非通过像add. 无需花费任何位来编码溢出情况的分支目标,唯一真正的选择是陷阱/异常。
(嗯,另一个更易于使用的选择是您可以设置的特殊寄存器,但随后上下文切换必须保存/恢复它。它必须像 JAL 一样工作来记录哪个添加错误。这可能需要微码,而 MIPS 避免了。或者至少对这种特殊情况进行更复杂的处理,因为它就像一个异常但不是异常,并且不能像这样的条件尽早检测到,beq因为它确实需要完整的 32 位相加结果.)
为什么 MIPS 的设计者将普通加法指令命名为addu?
我不知道,也许他们是硬件人员,而不是软件人员,并且过于乐观地认为他们将迎来一个检查算术的新时代,并使许多整数溢出错误成为过去。
事后看来,将add助记符赋予编译器始终使用的标准非陷阱版本将是最有意义的。然后,您需要为陷印版本取另一个名称。也许addt(诱捕)或addc(添加检查)。但是addc很糟糕,因为与其他 ISA 上的 add-with-carry 混淆,通常是adc. adds(添加签名)是一种可能性。起名好难!
基础知识:有一些明显的情况需要使用addu:
对于(可能)大的无符号整数,使用addu. 0x7fffffff + 1对未签名来说没什么特别的。但它是从 INT_MAX 到 INT_MIN 的 2 的补码溢出,因此add会陷入陷阱。
对于指针,使用addu. 除非您使用“高半内核”内存模型,否则一个数组不可能从其下方开始0x80000000并在其上方结束。
以下是我回答这个问题的第一个版本。其中部分内容与上述内容是多余的。但我认为不是全部。(TODO:完成编辑;我现在没有时间,但我认为现在发帖比等到以后再写更有用。)
当使用 addiu 优于 addi 时,反之亦然(以及为什么它更好)。
仅当您特别希望机器addaddi 在 2 的补码溢出(例如 INT_MAX + 1 变为 INT_MIN 之类的环绕)时捕获(又名故障,引发异常)时才使用/ 。
大多数时候没有人想要这个,但也许如果你在 asm 中手写并想要防御一些你可能使用的整数溢出错误add,如果引发异常比继续使用坏值更好。当然,这仅适用于add,而不是左移超过 1 或其他指令。因此,如果您真的想要在某些防御性代码中进行全面的整数溢出检查,那么无论如何您都需要另一种机制。
为了这是一个好主意,您可能希望为该硬件异常安装一个中断处理程序。(或者在操作系统下的用户空间中,SIGFPE 的信号处理程序,算术异常的 POSIX 信号。)
或者在开发过程中,也许您希望整数溢出在您希望不会溢出的添加指令上突然停止。即像一个assert。编译器不这样做是因为有时只进行检查而不检查所有内容的整数运算会很奇怪。但用手可能总比没有好。
整数溢出错误经常发生的一个地方是内存分配大小计算。(例如,导致小分配和程序结束它可能是 DOS 错误,或者取决于分配器的内存覆盖错误)。但是那些经常使用无符号整数;这不是0x7fffffff + 1 = 0x80000000与未签名有关的错误。
否则 AWAYS 使用addu/addiu
addu是 MIPS 的正常加法指令。
正如add 和 addu 之间的区别所指出的那样,这些名称具有误导性。可能对应于有符号溢出是 C 中的u未定义行为,但无符号溢出被明确定义为环绕。但如您所知,2 的补码加法与普通无符号二进制加法相同。 addi仅检查签名溢出。
(并不是说 C 编译器将永远使用addor addi:他们不想制作永远出错的代码。有符号溢出是 UB,但这并不意味着他们必须特别做任何事情。此外,在优化之后,制作 asm 并不少见具有 C 抽象机器中不存在的临时值。执行 w+x+y+z as(w+x)+(y+z)可能会导致有符号溢出,即使(((w+x)+y)+z)没有;二进制加法是真正关联的,无论有符号溢出如何。)
MIPS32r6 甚至去掉addi了,只留下了无故障的addiu。 (因此,如果您想捕获有符号溢出,您仍然可以将操作数放在寄存器中并使用。) 这在 Wikipediaadd上的项目符号列表中列出了已删除的不常用指令作为具有 16 位立即数的整数溢出捕获指令应该告诉您有关实际使用量的信息。addi
我的教授似乎只是为了提醒我们addiu可能存在而交替使用它们?
很难从中判断这是否真的是真的,或者他们是否有你没有注意到的微妙原因。例如addi,在绝对安全的情况下使用?
或者当他们考虑将整数视为有符号时,即使代码中只包含非负值?eg就C 抽象机而言for(int i=0 ; i<100; i++)是一个。signed int
在已知不可能有符号溢出的情况下(例如在 之后lui),这并不重要。addiu但是 IMO,除非你愿意,否则仍然总是使用。对我来说,作为人类阅读代码,理想情况下看到add/addi表示我们有意进行有符号溢出检查。但在 SO 问题中,更多的初学者只是使用add,因为他们没有考虑甚至不知道addu. 因此,它迫使您寻找“误报”陷阱错误的风险:add当您不希望它出现时,哪里可能出现故障。
我认为没有任何真正的 MIPS CPU比add/addi慢addu。addiu可能不是; 任何lw或sw可能出错取决于寄存器输入,因此 MIPS 流水线需要能够有效地处理可能出错的指令。
在一般情况下,他们从不出错;您为此优化了管道,实际上承担故障的效率有多低几乎无关紧要。(或者,在 MIPS 上使用软件 TLB 未命中处理可能确实很重要;这可能会相当频繁地发生,比页面错误更重要)。