我有一个作业,我们必须编写两个函数。还必须使用处理器的条件代码检测溢出条件,并返回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
此功能基本上是nCr = n! / (r! (n-r)!)
。当数字变大时,阶乘会发生溢出。
我只是不明白如何设置溢出条件。
1)您的算术命令是可能设置溢出位的操作
2)“ JO”(溢出时跳转)和“ JNO”(不溢出时跳转)允许分支,具体取决于是否发生溢出
3)您可能只是在“ JO”之后将“%eax”设置为0。
4)优秀的资源,如果您还不熟悉的话:
在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。例如:
seto %bl /* set bottom byte of B register to 1 if overflow flag set */
大多数指令在有符号溢出时设置为OF,在无符号溢出时设置为CF。 http://teaching.idallen.com/dat2343/10f/notes/040_overflow.txt说明加/减。 (像和/或/ xor之类的按位布尔指令不会溢出,因此它们总是清除CF和OF)。
当完整结果不是下半部分结果(与输入相同的宽度)的符号扩展时,imul
设置OF和CF。这甚至适用于高效的2操作数形式,该形式不会在任何地方写高半部分。他们仍然根据情况设置标志。如果对乘法进行无符号溢出检测,则需要使用笨拙的一操作数imul
。
当商不适合AL / AX / EAX / RAX(取决于操作数大小)时,除法会引发#DE异常。不幸的是,没有办法抑制/掩盖此现象,因此,除非有信号处理程序来捕获SIGFPE(在POSIX OS上),否则您不能尝试使用大分红的2N / N => N位除法并事后检测到溢出。 。或在裸机上,使用#DE的中断处理程序。
不是天真的计算mul
,而是可以更早地取消,而只需计算n!
。实际上使用较大的或prod(r+1 .. n)
或r
,然后除以另一个。
您仍然要除以一个可能的大数,因此这不会消除适合32位整数的所有可能结果发生溢出的机会。但是它扩展了您可以轻松处理的范围,并且当然会更快,因为您执行的乘法次数较少。例如n-r
只做C(999, 1000)
,所以不相乘,只有一个1000 / (1000-999)!
。
如果用div
指令对乘积进行最后的乘法运算以在EDX:EAX中产生64位结果,则可以将其用作32位除法的64位除数。 (如果您想冒例外的危险。)
mul
是NxN => 2N乘法,因此,如果仅在循环中使用它,它将忽略先前输出的上半部分。如果您按从低到高的顺序进行乘法运算,那么最后的乘法运算就是该范围的高端,这将为您提供最大的范围。
例如,您可能会这样做
mul
未经测试且未经过仔细考虑,可能是循环设置/范围内的一次性错误/特例错误。
这是我正在描述的那种事情:我们可以在累加产品时检查溢出,除了最后一步我们允许产生64位结果。
[可能在某些情况下,prod循环的最后一个// 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
将产生一个32位无符号结果,并设置了高位,但没有无符号溢出。使用imul / jo会错误地将其检测为溢出,因为它是signed
imul
。无论如何,这使我们在prod(10 .. 18)= mul
的情况下处理C(18, 9)
。最后一个指令将产生EAX = 0x41b9e4200
,它适合32位,而最后一个0x3a6c5900
将其乘以18以产生EDX:EAX = mul
(35个有效位)。然后,将其除以0x41b9e4200
,得到EAX = 0xbdec。
当9! = 0x58980
和n
较大时,EDX:EAX中的有效位数会更大(但要紧靠在一起,所以我们仍然避免溢出)。它们必须相距足够远,以使r
除数足够大,以使最终结果降低至适合32位。
否则,您将需要扩展精度除法,这可能...