assembly - ¿Cuándo y por qué firmamos extender y usar cdq con mul/div?
x86 sign-extension (1)
Use
cdq / idiv
para la
cdq / idiv
de 32 bits / 32 bits con
cdq / idiv
=> 32 bits,
xor edx,edx / div
para unsigned.
Si pone a cero EDX / RDX en lugar de extender la señal a EDX: EAX antes de
idiv
,
puede obtener un gran resultado positivo para -5 / 2, por ejemplo
.
Es posible utilizar la "potencia máxima" de 64/32 bits => división de 32 bits, pero no es seguro a menos que sepa que el divisor es lo suficientemente grande como para que el cociente no se desborde.
(es decir, en general no puede implementar
(a*b) / c
con solo
mul
/
div
y un temporal de 64 bits en EDX: EAX).
La división genera una excepción (#DE) en el desbordamiento del cociente.
En Unix / Linux,
el núcleo ofrece SIGFPE
para excepciones aritméticas, incluidos errores de división.
Con signo normal o división de extensión cero, el desbordamiento solo es posible
con
idiv
de
INT_MIN / -1
(es decir, el caso especial del complemento 2 del número más negativo).
Como puede ver en el manual de referencia de insn (enlace en el wiki de la etiqueta x86 ):
-
mul
/imul
un operando:edx:eax = eax * src
-
imul
dos operandos:dst *= src
. por ejemplo,imul ecx, esi
no lee ni escribe eax o edx.
-
div
/idiv
: divideedx:eax
por el src. cociente eneax
, resto enedx
. No hay forma dediv
/idiv
que ignoreedx
en la entrada. -
cdq
sign-extendeax
enedx:eax
, es decir, transmite el bit de signo deeax
en cada bit deedx
. No debe confundirse concdqe
, la instrucción de 64 bits que hacemovsx rax, eax
con menos bytes insn.Originalmente (8086), solo había
cbw
(ax = sign_extend(al)
) ycwd
(dx:ax = sign_extend(ax)
). Las extensiones de x86 a 32 bits y 64 bits han hecho que los mnemónicos sean un poco ambiguos (pero recuerde, aparte decbw
, las versiones dentro de eax siempre terminan con unae
para Extender). No hay instrucciones dl = sign_bit (al) porque 8bit mul y div son especiales, y usaax
lugar dedl:al
.
Como las entradas a
[i]mul
son registros únicos, nunca es necesario hacer nada con
edx
antes de multiplicar.
Si su entrada está firmada, la firma y la extiende para llenar el registro que está utilizando como entrada para la multiplicación, por ejemplo, con
movsx
o
cwde
(
eax = sign_extend(ax)
).
Si su entrada no está firmada, su extensión cero.
(Con la excepción de que si solo necesita los 16 bits bajos del resultado de la multiplicación, por ejemplo,
no importa si los 16 bits superiores de una o ambas entradas contienen basura
).
Para una división, siempre debe poner a cero o firmar extender eax en edx.
La extensión cero es lo mismo que simplemente poner a cero incondicionalmente edx, por lo que no hay instrucciones especiales para ello.
Solo
xor edx,edx
.
cdq
existe porque es mucho más corto que
mov edx, eax
/
sar edx, 31
para transmitir el bit de signo de eax a cada bit en edx.
Además, los cambios con conteo> 1 no existían hasta 286, por lo que en 8086, necesitaría un bucle si
cwd
no existiera (la versión de 16 bits de
cdq
).
En el modo de 64 bits, el signo y el cero que extienden los valores de 32 bits a 64 bits es común.
El ABI permite la basura en los 32 bits altos de un registro de 64 bits que contiene un valor de 32 bits, por lo que si se supone que su función solo debe mirar los 32 bits bajos de
edi
, no puede simplemente usar
[array + rdi]
para indexar la matriz.
Entonces, ve muchos
movsx rdi, edi
(extensión de signo) o
mov eax, edi
(extensión cero, y sí, es más eficiente usar un registro de destino diferente, porque Intel mov-elimination no funciona con
mov same,same
)
Tuve una prueba hoy y la única pregunta que no entendí fue convertir una palabra doble en una palabra cuádruple.
Eso me hizo pensar, ¿por qué / cuándo firmamos extender para multiplicación o división? Además, ¿cuándo usamos instrucciones como cdq?