La suma y resta en lenguaje ensamblador~~~~~~~~~ me ha preocupado durante mucho tiempo. . ¿Hay algún experto?
Respondí tu primera pregunta, pero no parecías satisfecho. No pensé que estuviera muy claro, así que eché un vistazo más de cerca:
Lee este artículo. Creo que lo entenderás:
Inicio del artículo:
Si piensas profundamente en este tema, realmente hay algo en ello. Por supuesto, si lo entiendes simplemente, lo entenderás. sea fácil. Ahora intentaré ampliar esto tanto como sea posible y hablar sobre ello en profundidad contigo.
1. ¡Solo hay un estándar!
A nivel de lenguaje ensamblador, al declarar una variable, no hay distinción entre con y sin signo. El ensamblador trata el literal entero que ingresa como un número con signo y lo almacena en el complemento de la computadora. ¡un estándar! El ensamblador no distingue entre firmado y no firmado y utiliza ambos estándares para procesarlo, ¡los trata a todos como firmados! ¡Y todos están compilados en códigos complementarios! En otras palabras, db -20 es: EC después del ensamblaje y db 236 también es EC después del ensamblaje. Hay un pequeño problema aquí. Los amigos que piensen profundamente encontrarán que db asigna un byte, por lo que el rango de enteros con signo que puede representar un byte es: -128 ~ 127. Entonces db 236 excede este rango, ¿cómo se puede permitir? Sí, el complemento de 236 excede el rango de representación de un byte, por lo que se puede acomodar con dos bytes (por supuesto, más bytes es mejor), que debería ser: 00 EC, lo que significa 236. El complemento de debería ser 00 EC, que no cabe en un byte. Sin embargo, no olvide el concepto de "truncamiento", que significa que el resultado final 00 EC es de dos bytes y se trunca a EC, por lo que es un "hermoso error". dices eso? Porque, cuando tratas 236 como un número sin signo, el resultado después del ensamblaje resulta ser EC. Ahora todos están contentos. Aunque el ensamblador solo usa un estándar para procesarlo, después de tomar prestado el hermoso error de "truncamiento", obtenemos El resultado. cumple con ambos criterios! Es decir, si le dan un byte y desea ingresar un número con signo, como -20, entonces el resultado del ensamblaje será correcto, si ingresa 236, entonces debe tratarlo como un número sin signo (porque 236; no es un Dentro del rango de números con signo que se pueden representar mediante bytes), el resultado obtenido también es correcto. Así que les daré una ilusión: el ensamblador tiene dos conjuntos de estándares. Distinguirá entre firmados y no firmados y luego los ensamblará por separado. De hecho, te han engañado. :-)
2. ¡Hay dos conjuntos de instrucciones!
El primer punto muestra que el ensamblador solo usa un método para ensamblar literales enteros en números de máquina reales. Pero eso no significa que la computadora no distinga entre números con y sin signo. Por el contrario, la computadora distingue muy claramente los números con y sin signo, porque la computadora tiene dos conjuntos de instrucciones como respaldo al procesar ciertas funciones. diferencia Preparado para números firmados y sin firmar. Sin embargo, se debe enfatizar aquí que la computadora no sabe si un número es un número con signo o sin signo. Depende de usted decidir cuando cree que el número que desea procesar está firmado, entonces lo usa. Un conjunto de instrucciones para procesar números firmados Cuando crea que el número que desea procesar no está firmado, utilice el conjunto de instrucciones para procesar números sin firmar. Solo hay un conjunto de instrucciones para suma y resta porque este conjunto de instrucciones se aplica tanto a operaciones con signo como sin signo. Las siguientes instrucciones: mul div movzx... manejan números sin signo, y estas: imul idiv movsx... manejan números con signo.
Por ejemplo:
Hay un byte x en la memoria: 0x EC, y un byte y: 0x 02. Al observar xey como números con signo, x = -20 e y = 2. Cuando se ven como números sin signo, x = 236 e y = 2.
A continuación, realice la operación de suma y use la instrucción agregar. El resultado obtenido es: 0x EE. Luego, este 0x EE se trata como un número con signo: -18, y el número sin signo es 238. Por lo tanto, una instrucción de suma se puede aplicar tanto a situaciones con signo como sin signo. (Jaja, ¿por qué necesitamos código complementario? Es solo para esto :-))
La operación de multiplicación no funcionará. Se deben usar dos conjuntos de instrucciones. Si hay un signo, el resultado obtenido por. usar imul es: 0x FF D8 es -40. Si usa mul sin signo, obtendrá: 0x 01 D8 es 472. (Consulte el Apéndice 2 para conocer las rutinas al final del artículo)
3. Lenguaje C lindo y aterrador.
¿Por qué se menciona c nuevamente? Debido a que la mayoría de los amigos que encuentran problemas firmados o no firmados son causados por declaraciones firmadas y no firmadas en C, entonces, ¿por qué comenzamos con el ensamblaje? Porque el compilador de C que usamos ahora, ya sea gcc o cl de vc6, compila el código del lenguaje C en código de lenguaje ensamblador y luego usa el ensamblador para ensamblarlo en código de máquina. Comprender el ensamblaje equivale a comprender fundamentalmente c. Además, para pensar en problemas con el pensamiento automático, se debe utilizar el ensamblaje. (Cuando encuentro algún problema extraño en el lenguaje C, normalmente lo compilo en un ensamblador para verlo).
C es encantador porque se ajusta al principio del beso y tiene el nivel justo de abstracción para la máquina. , Permitiéndonos Hemos mejorado el nivel de pensamiento (mucho más humano que el nivel de ensamblaje de la máquina), pero no estamos demasiado lejos de la máquina (como C #, Java, etc. están demasiado lejos). El problema de Kamp; es un ejemplo (Java no tiene este problema porque está diseñado para que todos los números enteros estén firmados). Para ilustrar las terribles características de c:
#include lt; stdio.hgt;
#include lt;
int main()
{
int x = 2;
char * str = "abcd";
int y = (x - strlen( str ) ) / 2;
printf("d\n", y);
}
El resultado debería ser -1 pero obtiene: 2147483647. ¿Por qué? Porque el valor de retorno de strlen es de tipo size_t, que es int sin signo. Cuando se mezcla con int, el tipo se convierte automáticamente y el resultado es naturalmente inesperado. . .
Observando el código compilado, la instrucción de división es div, que significa división sin signo.
La solución es forzar la conversión a int y = (int)(x - strlen(str) ) / 2; forzar la conversión a la dirección con signo (el compilador utiliza de forma predeterminada lo contrario). Las instrucciones de división se compilan en idiv. Sabemos que para dos unidades de memoria en el mismo estado, los resultados obtenidos al usar instrucciones de procesamiento firmadas imul, idiv, etc. son completamente diferentes de los resultados obtenidos al usar instrucciones de procesamiento sin firmar mul, div, etc. Entonces, cuando se trata de problemas relacionados con cálculos con y sin signo, especialmente cuando hay conversiones automáticas desagradables, ¡tenga mucho cuidado! (¡Cuando se realiza la conversión automática aquí, ni gcc ni cl aparecerán!)
Para evitar estos errores, se recomienda asegurarse de que sus variables estén firmadas cada vez que realice operaciones.
(Fin)
Apéndice
1: Por cierto, me gustaría señalar el error del camarada de arriba El documento PDF del Volumen 2 de "IA-32 Intel Architecture. Manual del desarrollador de software" se expresa como "signo extendido imm8", este signo extendido significa extensión de signo y se entiende como un número con signo. . .
Hablemos de extensión de signo: Cuando el operando se extiende en longitud, es necesario alargar el operando sin cambiar el valor original, por lo que aparece el término extensión de signo. Por ejemplo, movsx ax, 0xEC, después de la ejecución, el valor de ax es: 0xFFEC, la longitud se vuelve más larga, pero el resultado sigue siendo el mismo, es -20.
2: Dos conjuntos de rutinas de resultados de instrucciones de multiplicación
;; El programa se almacena como x.s
;; ------------------------------------------------
impresión externa
sección principal global
.data
str1: db "x", 0x0d, 0, 0
n: db 0x02
sección .text
principal:
xor eax, eax
mov al, 0xec p>
mul byte [n]; la instrucción de multiplicación con signo es: imul
push eax
push str1
call printf
agregar esp, byte 4
ret
;; --end----------------------- -- -------------------------------------
Pasos de compilación:
p>1. nasm -felf x.s
2. gcc x.o
Compilado con nasm y gcc en ubuntu7.04. Los resultados son consistentes con el artículo.
Finalmente:
Si cometo algún error, ¡corrígeme!
Mi blog: /band_of_brothers/
Fin del artículo
De hecho, siempre que comprenda que los números con signo y los números sin signo tienen una relación de complemento, el resultado suma y resta Los resultados son consistentes. La computadora no sabe si un número tiene signo o no, y si el mejor bit es 1 o 0 no significa nada para ella. El programador determina si un número tiene signo o no basándose en la situación real y luego selecciona el apropiado. instrucción. Por lo tanto, las instrucciones de suma y resta no sólo afectan a los bits de bandera CF y AF como números sin signo, sino que también afectan a las banderas OF y SF como números con signo. Luego, el programador elige diferentes bits de bandera para juzgar de acuerdo con la situación real.
Desbordamiento de número sin signo CF
Desbordamiento de número con signo OF