.intel_syntax noprefix
.global _start
.extern puts
.extern exit
.extern scanf
.extern printf
.extern stderr
.equ N, 42 # Constante
.text
_start:
lea rdi, fmt1
lea rsi, msg
call printf # Imprime mensaje
lea rdi, fmt2
lea rsi, v
call scanf # Lee número
mov r11d, [v]
cmp r11d, 0
jle error # Menor o igual que zero -> Error
mov r12d, N
cmp r11d, r12d
je bien # igual a N? -> Hemos terminado
jl menor
sub r11d, r12d
mov [h], r11d
lea rdi, menor_str
call puts
jmp muestra_pista
menor:
sub r12d, r11d
mov [h], r12d
lea rdi, mayor_str
call puts
muestra_pista:
mov r11d, [h]
cmp r11d, 5
jl pista_te_quemas
cmp r11d, 10
jl pista_caliente
cmp r11d, 15
jl pista_templado
lea rdi, frio_str
jmp pista
pista_te_quemas:
lea rdi, te_quemas_str
jmp pista
pista_caliente:
lea rdi, caliente_str
jmp pista
pista_templado:
lea rdi, templado_str
pista:
call printf
jmp fin
bien:
lea rdi, bien_msg
call puts
jmp fin
error:
mov rdi, stderr
lea rsi, err
call fprintf
fin:
call exit
.section .rodata
msg: .asciz "Adivina en que número estoy pensando ? "
err: .asciz "Solo números positivos\n"
fmt1: .asciz "%s"
fmt2: .asciz "%d"
bien_msg: .asciz "Bien Hecho!"
mayor_str: .asciz "Sigue Probando!!\nEl número es MAYOR"
menor_str: .asciz "Sigue Probando!!\nEl número es MENOR"
te_quemas_str: .asciz "TE QUEMAS!!\n"
caliente_str: .asciz "CALIENTE!\n"
templado_str: .asciz "TEMPLADO\n"
frio_str: .asciz "FRIO\n"
.section .data
h: .word 0
v: .word 0En ensamblador no tenemos ningún tipo de construcción de alto nivel
como bloques if... else, operadores ternarios,
switch o valores booleanos, si bien, como es lógico,
podemos simular toda esa funcionalidad.
La mayoría de ensambladores nos permiten definir constantes
utilizando la pseudo instrucción equ. La sintaxis puede
variar de un ensamblador a otro, pero es bastante común que esa sea la
forma estándar de definir una constantes.
El concepto de tipo no existe y por tanto no existen los valores
booleanos, los valores que obtenemos como resultado de una operación
lógica y que pueden contener valores 0 y 1, o VERDADEOR y
FALSO. Depende de nosotros como queramos representar, pero
la forma más habitual es utilizar el valor 1 para
TRUE/VERDADERO y 0 para FALSE/FALSO, puesto
que facilitan la implementación de las condiciones. Veamos como.
La mayoría de procesadores disponen de una serie de flags, banderas o
indicadores (como queráis llamarlos) que se activan dependiendo de
ciertas operaciones. Algunos de los más comunes son el flag
Zero que se activa cuando el resultado de una operación es
cero, o el flag Sign que se activa según el signo del
resultado de una operación.
SABÍAS QUE
No todos los procesadores tienen flags que nos indican el resultado de las operaciones aritméticas y lógicas. Los procesadores intel utilizan flags para este propósito. Otros procesadores como MIPS o RISC-V no usan flags y solo ofrecen saltos condicionales en los que debemos indicar explícitamente los registros que queremos comparar y el tipo de comparación
La forma más habitual para activar los flags y así poder comparar
datos, es utilizar la instrucción CMP que permite comparar
dos valores. Esta instrucción básicamente resta los dos valores que le
pasamos, pero sin almacenar el resultado. Por ejemplo, la expresión:
if (a == b) puts ("Iguales"); else puts ("Diferentes");Se traduce en:
mov rax, [a]
mov rbx, [b]
cmp rax, rbx
je iguales # Si a=b cmp pone el flag Zero a 1
# Este es el caso else a!=b
lea rdi, diferentes_str
jmp cont
iguales:
lea rdi, iguales_str
cont:
call putsAunque a nivel de código fuente tenemos que escribir mucho más, el código que se genera finalmente suele ser más corto que para la expresión equivalente en un lenguaje de alto nivel.
El flag Z o Zero es uno de los más
sencillos de usar y es por ello que si elegimos 0 como valor falso y
cualquier otro valor como verdadero, podemos comparar valores
“boleanos” usando el flag Z.
Por ejemplo, en C encontramos habitualmente expresiones como:
if (a) goto es_verdadero; else goto es_falso;Que se traducen directamente en esto:
cmp BYTE [a], 0
jz es_falso
; Caso es verdadero
es_falso:
; Caso es falsoUn operador ternario tendría una forma muy similar, pero habilitando alguna forma de devolver el valor.
De la misma forma un switch se puede implementar como
una secuencia de ifs:
switch (ax) {
case 0:
bx = 0;
break;
case 1:
bx = 1
case 2:
bx = 2;
break;
default
bx = 3;
}Que en ensamblador sería algo como esto:
cmp ax, 0
je caso1
cmp ax, 1
je caso1
cmp ax, 2
je caso2
jmp default
caso0:
mov bx, 0
jmp fin # break
caso1:
mov bx, 1 # caso sin break... la ejecución continua
caso2:
mov bx, 2
jmp fin # break
default:
mov bx, 3
fin :
# ContinuaEn ese fragmento de código podéis ver porque si nos olvidamos el
break (jmp fin realmente) el programa sigue a
través de los distintos casos del switch.
En el programa de ejemplo podríamos haber usado la función
abs de la librería estándar para calcular el valor absoluto
de la pista, sin embargo, en este caso tan sencillo, puesto que ya
habíamos hecho la comparación, ya sabemos que número es mayor y
simplemente invertimos la resta dependiendo del caso:
mov r11d, [v]
(...)
mov r12d, N
cmp r11d, r12d
je bien # V == N? -> Hemos terminado
jl menor # V < N vamos a menor
sub r11d, r12d # Restamos V-N para que el resultado sea poisitvo
mov [h], r11d
(...)
menor:
sub r12d, r11d # Restamos N -V para que el resultado sea positivo
mov [h], r12dUna forma alternativa de implementar esto sería:
mov r11d. [v]
mov r12d, N
sub r11d, r12d
mov [h], r11d # Tenemos que almacenarlo ya que es modificado
# por un puts intermedio
(...)
mov r11d, [h]
mov r12d, r11d
neg r11d
cmovl r11d, r12dEn este caso usamos la instrucción cmovl que se ejecuta
dependiendo de los valores de los flags. El sufijo l
significa Less Than o Menor que. Si el valor
h almacenado en r11d fuera negativo, al
negarlo con NEG pasaría a ser positivo y por lo tanto
cmovl no se ejecutaría. Por el contrario, si el valor en
r11d fuera positivo, al negarlo pasaría a ser negativo y
cmovl se ejecutaría restaurando el valor original
(positivo) almacenado en r12d temporalmente.
SABÍAS QUE
La instrucción
CMOVccfue introducida en la familia de procesadores P6 (Pentium Pro, Pentium II/III), por lo que no puede usarse con procesadores anteriores como el i386, i486 o los procesadores Pentium P5 (los Pentium normales).
Como podéis ver, en ensamblador tenemos una gran flexibilidad para
hacer las cosas de diferentes formas, si bien esa característica es una
de las cosas que hacen la programación en ensamblador complicada.
Además, ahora también podéis ver porque ciertas cosas en el lenguaje C
son como son. Por ejemplo el considerar valores 0 como falsos y
cualquier otro valor como verdadero, o porque es necesario poner
break en cada caso del switch.
Resumen
- No soporta
if ... [elsif] ...[else] ... - No Soporta operador terciario
- No soporta booleanos:
==0 -> FALSO y !=0 VERDADERO - No soporta
switch - Soporta constantes si bien la sintaxis depende del ensamblador usado.
■
