Lenguaje ensamblador - Estructuras de bucle
Los bucles son una de las estructuras de control más comunes en los programas. En lenguaje ensamblador, puedes usar la instrucción LOOP o saltos condicionales para implementar diversas lógicas de bucle.
La instrucción LOOP
LOOPEs una instrucción diseñada específicamente para bucles en x86; utiliza ECX como contador:
Cada vez que se ejecuta LOOP, ECX se decrementa automáticamente en 1; si ECX no es 0, salta a la etiqueta de destino.
Ejemplo
; Uso básico de la instrucción LOOP: repetir la salida 5 veces
section .data
msg db 'ejemplo', 0xA
len equ $ - msg
section .text
global _start
_start:
mov ecx, 5 ; Contador de bucle = 5 (bucle de 5 veces)
repeat:
; Guardar ecx (las llamadas al sistema pueden modificarlo)
push ecx
; Imprimir msg
mov eax, 4
mov ebx, 1
mov ecx, msg
mov edx, len
int 0x80
; Restaurar ecx
pop ecx
loop repeat ; ecx--; si ecx != 0 salta a repeat
mov eax, 1
mov ebx, 0
int 0x80
Resultado de la ejecución:
$ nasm -f elf32 loop_basic.asm -o loop_basic.o $ ld -m elf_i386 loop_basic.o -o loop_basic $ ./loop_basic ejemplo ejemplo ejemplo ejemplo ejemplo
loopPor defecto se usa ECX (32 bits) como contador. En modo de 16 bits se usa CX, y en modo de 64 bits se usa RCX. Asegúrate de establecer correctamente el valor de ECX antes de loop.
Variantes de LOOP
| Instrucción | Condición de salto | Descripción |
|---|---|---|
| LOOP | ECX != 0 | Bucle estándar: primero decrementa ECX y luego comprueba |
| LOOPE / LOOPZ | ECX != 0 y ZF = 1 | Continúa el bucle si son iguales |
| LOOPNE / LOOPNZ | ECX != 0 y ZF = 0 | Continúa el bucle si no son iguales |
Ejemplo
section .data
array db 0, 0, 0, 5, 0, 0 ; Los primeros tres son 0, el cuarto es 5
section .text
global _start
_start:
mov ecx, 6 ; Longitud del array
mov esi, array - 1 ; Apunta a la posición anterior al array
find_nonzero:
inc esi ; Moverse al siguiente elemento
cmp byte [esi], 0 ; ¿El elemento actual == 0?
loope find_nonzero ; Si es 0 y ecx > 0, continúa el bucle
; Fin del bucle: se encontró el primer elemento no nulo, o se terminó el recorrido
; esi ahora apunta a la dirección del primer elemento no nulo
; ecx restante: número de elementos no comparados
mov eax, 1
mov ebx, 0
int 0x80
Bucle condicional (bucle WHILE)
Implementar un bucle while con saltos condicionales: primero se evalúa la condición y luego se ejecuta el cuerpo del bucle.
Ejemplo
; Implementación: while (x < 100) x = x * 2;
section .data
x dd 1
limit dd 100
section .text
global _start
_start:
; Bucle while
while_start:
mov eax, [x] ; Cargar x
cmp eax, [limit] ; x < 100 ?
jge while_end ; Si x >= 100, terminar el bucle
shl eax, 1 ; x = x * 2
mov [x], eax ; Guardar x de vuelta
jmp while_start ; Volver al inicio del bucle y evaluar de nuevo
while_end:
; x ahora es 128 (1->2->4->8->16->32->64->128, 128 >= 100 se detiene)
mov eax, 1
mov ebx, [x] ; Devolver x como código de salida
int 0x80
Bucle DO-WHILE
Primero se ejecuta el cuerpo del bucle y luego se evalúa la condición:
Ejemplo
section .data
counter dd 0
section .text
global _start
_start:
mov dword [counter], 0
do_loop:
; Cuerpo del bucle: counter++ (se ejecuta al menos una vez)
inc dword [counter]
; Evaluar condición
cmp dword [counter], 5
jl do_loop ; Continuar mientras counter < 5
; counter al final = 5
mov eax, 1
mov ebx, 0
int 0x80
Bucles anidados
En los bucles anidados hay que prestar atención al guardado del contador externo:
Ejemplo
; Ejemplo de bucle anidado: imprimir la tabla de multiplicar (3×3)
section .data
space db ' '
newline db 0xA
; Para almacenar los caracteres numéricos convertidos (2 dígitos + espacio + null)
num_str db ' ', 0
section .text
global _start
_start:
mov ecx, 3 ; Contador del bucle externo (número de filas)
outer_loop:
push ecx ; ★ ¡Guardar el contador externo! (Importante)
mov ecx, 3 ; Contador del bucle interno (número de columnas)
inner_loop:
push ecx ; Guardar el contador interno
; Calcular el producto = número de fila externo (4-ecx actual) × número de columna interno
; Aquí se omite la conversión y salida concreta de números; solo se muestra la estructura
; Salida real: número de fila × número de columna
pop ecx ; Restaurar el contador interno
loop inner_loop ; Bucle interno
; Imprimir nueva línea
mov eax, 4
mov ebx, 1
mov ecx, newline
mov edx, 1
int 0x80
pop ecx ; ★ Restaurar el contador externo
loop outer_loop ; Bucle externo
mov eax, 1
mov ebx, 0
int 0x80
El error más común en bucles anidados es olvidar guardar el valor del contador externo en la pila. LOOP modifica ECX; cuando vuelves a establecer ECX en el bucle interno, el valor del contador externo se pierde. La solución es, antes de entrar en el bucle interno
push ecx, y después de salirpop ecx。
Técnicas de optimización de bucles
Algunos métodos para mejorar la eficiencia de los bucles:
Ejemplo
; Método A: usar la instrucción LOOP (más lento, pero no notable)
mov ecx, 1000
loop_a:
add eax, [ebx]
add ebx, 4
loop loop_a
; Método B: usar saltos condicionales (conteo manual) (generalmente más rápido)
mov ecx, 1000
loop_b:
add eax, [ebx]
add ebx, 4
dec ecx
jnz loop_b
; Método C: desenrollado de bucles (el más rápido, pero con mayor tamaño de código)
; Desenrollar 1000 iteraciones del bucle para procesar 4 elementos cada vez
mov ecx, 250 ; 1000 / 4 = 250
loop_unrolled:
add eax, [ebx]
add eax, [ebx + 4]
add eax, [ebx + 8]
add eax, [ebx + 12]
add ebx, 16
dec ecx
jnz loop_unrolled
; Método D: conteo descendente (reduce las instrucciones de comparación)
mov ecx, 1000
lea ebx, [array + 1000*4 - 4] ; Comenzar desde el final del array
loop_reverse:
add eax, [ebx]
sub ebx, 4
dec ecx
jnz loop_reverse
Ejemplo completo: calcular la suma del 1 al 100
Ejemplo
; Calcular 1+2+...+100 = 5050
section .data
msg db 'Sum of 1 to 100 is: '
msg_len equ $ - msg
newline db 0xA
section .bss
result_str resb 12 ; Almacenar la cadena numérica convertida
section .text
global _start
_start:
; Calcular la suma acumulada
mov ecx, 100 ; Bucle 100 veces
mov eax, 0 ; La suma acumulada inicial es 0
mov ebx, 1 ; El número actual a sumar
sum_loop:
add eax, ebx ; eax += ebx
inc ebx ; Siguiente número
loop sum_loop ; Continuar el bucle
; eax = 5050
; Imprimir texto de aviso
push eax ; Guardar el resultado
mov eax, 4
mov ebx, 1
mov ecx, msg
mov edx, msg_len
int 0x80
pop eax ; Restaurar el resultado
; Convertir el número a cadena (versión simple: imprimir directamente el valor entero)
; Aquí se simplifica: convertir el byte bajo de eax a carácter
add al, '0' ; No es exacto, solo es una demostración
mov [result_str], al
; Imprimir el resultado
mov eax, 4
mov ebx, 1
mov ecx, result_str
mov edx, 12
int 0x80
mov eax, 4
mov ebx, 1
mov ecx, newline
mov edx, 1
int 0x80
mov eax, 1
mov ebx, 0
int 0x80
Otras extensionesConvertir números a cadenas es un problema común en ensamblador. El método simple anterior solo funciona cuando el número está entre 0 y 9. El algoritmo completo de conversión de números a cadenas se explicará en detalle en el capítulo "Procesamiento de números".