我有一项任务,我们必须编写两个函数。还必须使用处理器的条件代码检测溢出条件,并返回0
以指示已遇到错误。我能够写出这些功能。
.file "formula.c"
.text
.globl _nCr
.def _nCr; .scl 2; .type 32; .endef
_nCr:
pushl %ebp
movl %esp, %ebp
subl $56, %esp
movl 8(%ebp), %eax
movl %eax, (%esp)
testl %eax, %eax
call _factorial
movl %eax, -12(%ebp)
movl 12(%ebp), %eax
addl $1, %eax
movl %eax, (%esp)
call _factorial
movl %eax, -16(%ebp)
movl 12(%ebp), %eax
notl %eax
addl 8(%ebp), %eax
movl %eax, (%esp)
call _factorial
movl %eax, -20(%ebp)
movl -16(%ebp), %eax
movl %eax, %edx
imull -20(%ebp), %edx
movl %edx, -28(%ebp)
movl -12(%ebp), %eax
movl %eax, %edx
sarl $31, %edx
idivl -28(%ebp)
leave
ret
.globl _factorial
.def _factorial; .scl 2; .type 32; .endef
_factorial:
pushl %ebp
movl %esp, %ebp
subl $16, %esp
movl $1, -8(%ebp)
movl $1, -4(%ebp)
jmp L3
L4:
movl -8(%ebp), %eax
imull -4(%ebp), %eax
movl %eax, -8(%ebp)
addl $1, -4(%ebp)
L3:
movl -4(%ebp), %eax
cmpl 8(%ebp), %eax
jle L4
movl -8(%ebp), %eax
leave
ret
.def ___main; .scl 2; .type 32; .endef
.section .rdata,"dr"
.align 4
此功能基本上是n!/r!(n-r)!
。当数字变大时,溢出发生在阶乘。我只是不明白我将如何设置溢出条件。
答案 0 :(得分:2)
1)您的算术命令是可能设置溢出位的操作
2)" JO" (跳过溢出)和" JNO" (跳过不溢出)允许你分支,这取决于是否发生溢出
3)你可能只是设置"%eax" " JO"。
之后的04)如果您还不熟悉它,那就是优秀,优秀的资源:
答案 1 :(得分:1)
在x86架构上,当执行诸如addl 8(%ebp), %eax
之类的算术指令时,在CPU状态字中设置条件代码。有些指令的行为取决于条件代码。
您可以让代码在给定条件下采用备用路径(执行分支)。 x86在Jxx
助记符JA, JAE, JB, JBE, JC, JCXZ, ..., JZ
下有一系列条件分支指令。例如JZ
表示如果为零则跳转:如果指令产生零结果,则设置分支,设置零标志。 JO
在溢出时跳跃。
条件也可以转换为字节数据并存储到寄存器或存储器中。这对于编译C表达式很有用,例如:
x = (y != 3); /* if (y != 3) x = 1; else x = 0 */
它由SETx
指令组完成,这些指令也很多,如条件分支:SETA, SETAE, SETB, ..., SETZ
。例如,如果零条件为真,SETZ将给定字节设置为1。 E.g。
seto %bl /* set bottom byte of B register to 1 if overflow flag set */
答案 2 :(得分:0)
大多数指令在有符号溢出时设置OF,而在无符号溢出时设置CF。 http://teaching.idallen.com/dat2343/10f/notes/040_overflow.txt解释添加/订阅。 (按位布尔指令(例如和/或/ xor不会溢出,因此它们总是清除CF和OF)。
imul
在整个结果不是下半部分结果的符号扩展(与输入宽度相同)时同时设置OF和CF。这甚至适用于高效的2操作数形式,该形式不会在任何地方写高半部分。他们仍然根据情况设置标志。如果对乘法进行无符号溢出检测,则需要使用笨拙的单操作数mul
。
当商不适合AL / AX / EAX / RAX(取决于操作数大小)时,除会引发#DE异常。不幸的是,没有办法抑制/屏蔽它,因此除非有信号处理程序来捕获SIGFPE(在POSIX OS上),否则您不能尝试使用大分红的2N / N => N位除法并在事后检测到溢出。 。或者在裸机上,使用#DE的中断处理程序。
您不必天真地计算n!
,而是可以更早取消并只计算prod(r+1 .. n)
。实际使用较大的r
或n-r
,然后除以另一个。
最后还是要除以一个可能的大数,这样就不能消除所有适合32位整数的可能结果的溢出机会。但是它扩展了您可以轻松处理的范围,并且当然会更快,因为您要做的乘法次数更少。例如C(999, 1000)
只做1000 / (1000-999)!
,所以没有乘法,只有一个div
。
如果使用mul
指令对乘积进行最后的乘法以在EDX:EAX中产生64位结果,则可以将其用作32位除法的64位除数。 (如果您想冒例外的危险。)
mul
是NxN => 2N乘法,因此,如果仅在循环中使用它,它将忽略先前输出的上半部分。如果您按从低到高的顺序进行乘法运算,那么最后的乘法运算就是该范围的高端,这将为您提供最大的范围。
例如,您可能会这样做
// mix of C pseudocode and AT&T 32-bit.
// Some real registers, some var names: pick registers for those.
if (n == r) return 1;
divisor = factorial(min(r, n-r)); // and save in another register until later
eax = max(r,n-r) + 1; // prod
xor %edx, %edx # in case we skip the mul
cmp n, %eax
jae endprod # loop might need to run 0 times, but n==r case already handled
lea 1(%eax), %ecx # i = low+1. Not overflow-checked
jmp loop_entry
prod_loop: # do{
imul %ecx, %eax # prod *= i for i=[max+2 .. n-1]
jo overflow_in_prod # maybe need mul to avoid spurious signed but not unsigned overflow cases
inc %ecx
loop_entry:
cmp n, %ecx
jb prod_loop # }while(i<n)
# leave the loop with ECX = n, with one multiply left to do
mul %ecx # EDX:EAX = n * EAX
# We're keeping the full 64-bit result, therefore this can't overflow
endprod:
div divisor # prod /= the smaller factorial
# EDX:EAX / divisor, quotient in EAX. Or will raise #DE if it didn't fit.
ret
overflow_in_prod:
do something
ret
未经测试并且没有仔细考虑的问题,可能是循环设置/边界中的一个错误/异常案例。
这就是我正在描述的事情:我们可以在累加产品时检查溢出,除了最后一步,我们可以产生64位结果。
在某些情况下,prod循环的最后一个imul
会产生高位设置的32位无符号结果,但没有无符号溢出。使用imul / jo会错误地将其检测为溢出,因为它是 signed 溢出的。最后的div不会溢出。因此,如果您不仅关心速度,还可以在那里使用(稍微慢一些)mul
。
无论如何,这使我们可以处理C(18, 9)
,其中prod(10 .. 18)= 0x41b9e4200
。最后一个指令将生成EAX = 0x3a6c5900
,它适合32位,而最后一个mul
将其乘以18以产生EDX:EAX = 0x41b9e4200
(35个有效位)。然后,将其除以9! = 0x58980
,得到EAX = 0xbdec。
当n
和r
很大时,EDX:EAX中的有效位数甚至可以更大(但要紧靠在一起,这样我们仍然可以避免溢出)。它们必须相距足够远,以使(n-r)!
除数足够大,以使最终结果回落到32位。
否则,您将需要扩展精度除法...