← SEARCHABLE ARCHIVE
Assembly · 2026-08-15

Leer bucles x86-64 de arrays, cadenas y estructuras

Seguir contratos puntero-longitud, strides, sentinelas y límites en bucles reales

INTERACTIVE READING MODE →

Leer bucles x86-64 de arrays, cadenas y estructuras

Este artículo se ha creado con fines didácticos. Los ejemplos usan sintaxis Intel; validez del puntero, vida del objeto, aliasing y overflow siguen siendo contratos de la frontera C/C++.

Muchos bucles binarios avanzan desde una dirección inicial con stride constante mientras prueban o acumulan valores. Arrays, cadenas y arrays de estructuras comparten este patrón, pero cambian la terminación y el stride.

Encontrar cuatro estados

  1. Puntero base o actual
  2. Índice, puntero final o cuenta restante
  3. Acumulador o resultado de búsqueda
  4. Condición de salida y signedness
xor eax, eax
xor r8d, r8d
loop_test:
    cmp r8, rdx
    jae loop_done
    add eax, dword ptr [rcx + r8*4]
    inc r8
    jmp loop_test
loop_done:
    ret

rcx es la base, r8 el índice, eax el acumulador y unsigned r8 >= rdx termina el bucle.

Índice y puntero final

lea rdx, [rdi + rsi*4]
xor eax, eax
cmp rdi, rdx
je done
loop_begin:
    add eax, dword ptr [rdi]
    add rdi, 4
    cmp rdi, rdx
    jne loop_begin
done:
    ret

Aunque no hay índice explícito, el rango se recupera desde el puntero inicial, el paso de cuatro bytes y el final calculado. La suma máquina puede hacer wrap, pero la aritmética de punteros C/C++ tiene un contrato de objeto más estrecho.

Longitud y centinela

Una cadena C termina con un byte cero.

strlen_like:
    mov rax, rcx
scan:
    cmp byte ptr [rax], 0
    je found
    inc rax
    jmp scan
found:
    sub rax, rcx
    ret

Sin longitud máxima se asume que hay un NUL dentro de memoria legible. No confunda bytes, code units UTF-8, code points Unicode y grapheme clusters.

Si “no encontrado” retorna -1 con tipo size_t, el patrón de bits es SIZE_MAX. Caller y callee deben interpretar igual el -1 signed y el máximo unsigned.

Stride y offset de campo

xorps xmm0, xmm0
test rdx, rdx
jz done
loop_begin:
    cmp dword ptr [rcx + 12], 0
    je skip
    addss xmm0, dword ptr [rcx]
skip:
    add rcx, 16
    dec rdx
    jne loop_begin
done:
    ret

Offset 12 es candidato a campo, offset 0 a float y paso 16 a tamaño de estructura. Son pruebas del layout binario actual y se verifican con static_assert y la información del compilador.

AoS suele mostrar una base con stride y varios offsets; SoA, bases independientes por campo. Para procesar masivamente un solo campo, cambiar el layout puede superar a ASM manual.

Unrolling, vector y tail

loop4:
    add eax, [rcx]
    add eax, [rcx + 4]
    add eax, [rcx + 8]
    add eax, [rcx + 12]
    add rcx, 16
    sub rdx, 4
    jae loop4

Una iteración máquina procesa cuatro elementos. Suele existir un remainder para 0–3; no deduzca que la longitud es múltiplo de cuatro sin revisarlo.

movdqu xmm0, [rcx]
paddd  xmm1, xmm0

paddd significa cuatro lanes de 32 bits, no un entero de 128 bits. Un bucle vectorizado suele tener prólogo, main SIMD y tail escalar.

Overlap y overflow de tamaño

memcpy exige no solapamiento, memmove debe soportarlo y un bucle tipado añade contratos de vida, alineación y aliasing.

bytes = count * sizeof(Item);

Si la multiplicación hace wrap a una asignación pequeña y el bucle usa el count original, puede causar heap overflow. Audite juntos tamaño y límite, validando count <= SIZE_MAX / sizeof(Item).

Resumen

Los bucles de datos se dividen en puntero, stride, acumulador y terminación. Los centinelas sustituyen una longitud explícita por la existencia obligatoria del terminador. Entrada vacía, tail, overlap, unidades y overflow forman parte del contrato completo.

Fuentes oficiales