Leer bucles x86-64 de arrays, cadenas y estructuras
Seguir contratos puntero-longitud, strides, sentinelas y límites en bucles reales
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
- Puntero base o actual
- Índice, puntero final o cuenta restante
- Acumulador o resultado de búsqueda
- 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.