assembly - register - x86 montaje abs() implementación?
registers assembly x86 (8)
Necesito obtener la diferencia de 2 enteros con signo. ¿Existe una función ABS () en lenguaje ensamblador x86, así que puedo hacer esto? Cualquier ayuda sería muy apreciada.
ABS (EAX)
test eax, eax ; Triger EFLAGS [CF, OF, PF, SF, and ZF]
jns AbsResult ; If (SF) is off, jmp AbsResult
neg eax ; If (SF) is on. (negation nullify by this opcode)
AbsResult:
Si los indicadores ya están establecidos por lo que haya generado el valor en eax, no necesita la test
. Las predicciones erróneas de la rama harán esto lento si los valores de entrada se distribuyen aleatoriamente entre positivo y negativo.
Esto funciona de la misma manera para RAX, AX, AL.
Ahí está la instrucción SUB, si lo que quieres es hacer AB. HTH
Hilo viejo, pero si he navegado aquí tarde, es posible que también tengas ... abs es un ejemplo brillante, así que esto debería estar aquí.
; abs(eax), with no branches.
; intel syntax (dest, src)
mov ebx, eax ;store eax in ebx
neg eax
cmovl eax, ebx ;if eax is now negative, restore its saved value
Si desea manejar todos los casos correctamente, no puede simplemente restar y luego tomar el valor absoluto. Tendrá problemas porque la diferencia de dos enteros con signo no es necesariamente representable como un entero con signo. Por ejemplo, supongamos que está utilizando enteros del complemento de 32 bits 2s y desea encontrar la diferencia entre INT_MAX
( 0x7fffffff
) y INT_MIN
( 0x80000000
). La resta da:
0x7fffffff - 0x80000000 = 0xffffffff
que es -1
; cuando toma el valor absoluto, el resultado que obtiene es 1
, mientras que la diferencia real entre los dos números es 0xffffffff
interpretada como un entero sin signo ( UINT_MAX
).
La diferencia entre dos enteros con signo siempre se puede representar como un entero sin signo. Para obtener este valor (con hardware de complemento 2s), simplemente reste la entrada más pequeña de la más grande e interprete el resultado como un entero sin signo; No hay necesidad de un valor absoluto.
Aquí hay una (de muchas, y no necesariamente la mejor) manera de hacer esto en x86, asumiendo que los dos enteros están en eax
y edx
:
cmp eax, edx // compare the two numbers
jge 1f
xchg eax, edx // if eax < edx, swap them so the bigger number is in eax
1: sub eax, edx // subtract to get the difference
Si es un ensamblaje x86, lo siguiente de acuerdo con la wikipedia siempre útil debería funcionar. Reste un valor de otro y luego use estas instrucciones en el resultado:
cdq
xor eax, edx
sub eax, edx
Suponiendo que sus enteros están en registros MMX o XMM, use psubd
para calcular la diferencia, luego pabsd
para obtener el valor absoluto de la diferencia.
Si sus enteros están en el plano, los registros "normales", luego hacen una resta, luego el truco de cdq
para obtener el valor absoluto. Esto requiere el uso de algunos registros específicos ( cdq
sign-extiende eax
en edx
, no utilizando ningún otro registro), por lo que es posible que desee hacer cosas con otros códigos de operación. P.ej:
mov r2, r1
sar r2, 31
calcula en el registro r2
la extensión de signo de r1
(0 si r1
es positivo o cero, 0xFFFFFFFF si r1
es negativo). Esto funciona para todos los registros de 32 bits r1
y r2
y reemplaza la instrucción cdq
.
Una forma corta pero directa, usando la instrucción de movimiento condicional (Pentium disponible y superior, creo):
; compute ABS(r1-r2) in eax, overwrites r2
mov eax, r1
sub eax, r2
sub r2, r1
cmovg eax, r2
La instrucción secundaria establece los indicadores de la misma manera que la instrucción cmp.
Así es como la función de biblioteca C abs()
hace en ensamblado sin bifurcar:
abs(x) = (x XOR y) - y
donde y = x >>> 31
(suponiendo una entrada de 32 bits), y >>>
es un operador aritmético de desplazamiento a la derecha.
Explicación de la fórmula anterior: Queremos generar el complemento de 2 de x
negativo solamente.
y = 0xFFFF, if x is negative
0x0000, if x is positive
Entonces cuando x
es positivo x XOR 0x0000
es igual a x
. Y cuando x
es negativo, x XOR 0xFFFF
es igual al complemento de 1 de x
. Ahora solo necesitamos agregar 1
para obtener el complemento de 2, que es lo que está haciendo la expresión -y
. Porque 0xFFFF
es -1 en decimal.
Veamos el ensamblado generado para el siguiente código por gcc
(4.6.3 en mi máquina):
Código C:
main()
{
int x;
int output = abs(x);
}
gcc 4.6.3 generó un fragmento de ensamblaje (sintaxis de AT&T), con mis comentarios:
movl -8(%rbp), %eax # -8(%rbp) is memory for x on stack
sarl $31, %eax # shift arithmetic right: x >>> 31, eax now represents y
movl %eax, %edx #
xorl -8(%rbp), %edx # %edx = x XOR y
movl %edx, -4(%rbp) # -4(%rbp) is memory for output on stack
subl %eax, -4(%rbp) # (x XOR y) - y
BONIFICACIÓN (de Hacker''s Delight ): si tienes una multiplicación rápida por +1 y -1, lo siguiente te dará abs(x)
:
((x >>> 30) | 1) * x