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

; Ruta del archivo: loop_basic.asm
; 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ónCondición de saltoDescripción
LOOPECX != 0Bucle estándar: primero decrementa ECX y luego comprueba
LOOPE / LOOPZECX != 0 y ZF = 1Continúa el bucle si son iguales
LOOPNE / LOOPNZECX != 0 y ZF = 0Continúa el bucle si no son iguales

Ejemplo

; Ejemplo de LOOPE: buscar el primer valor no nulo en un array

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

; Ruta del archivo: while_loop.asm
; 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

; Bucle DO-WHILE: se ejecuta al menos una vez

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

; Ruta del archivo: nested_loop.asm
; 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 internopush 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

; Comparación de optimización de bucles

; 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

; Ruta del archivo: sum_1_to_100.asm
; 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

Convertir 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".

Otras extensiones