Volver al inicio

Devolviendo Estructuras x86-64: Registros y MEMORY ABI

Este artículo desglosa los mecanismos de retorno de tipos fundamentales y estructuras desde funciones en x86-64 según System V ABI. Describe las reglas de clasificación INTEGER y MEMORY, uso de registros rax/rdx y argumento rdi oculto. Ejemplos de ensamblador sin optimizaciones.

x86-64: cómo devolver estructuras vía rax, rdx y rdi
Advertisement 728x90

Cómo x86-64 devuelve valores usando la ABI System V

Veamos el caso básico de devolver un int:

one_plus_one():
  mov DWORD PTR [rbp-4], 2
  mov eax, DWORD PTR [rbp-4]
  ret

El valor se carga en eax (los 32 bits inferiores de rax). De forma similar, para tipos de 64 bits, se utiliza todo el registro rax.

Estructuras pequeñas hasta 16 bytes: devolución por registros

Las estructuras pequeñas se devuelven directamente en rax y rdx. Consideremos una estructura de 16 bytes:

Google AdInline article slot
struct nums {
    std::int64_t first{};
    std::int64_t second{};
};
nums construct() {
    nums ret{10, 120};
    return ret;
}

Código ensamblador generado:

construct():
  mov QWORD PTR [rbp-16], 10
  mov QWORD PTR [rbp-8], 120
  mov rax, QWORD PTR [rbp-16]
  mov rdx, QWORD PTR [rbp-8]
  ret

Los campos se disponen secuencialmente: first va a rax, second a rdx. El orden de memoria sigue el formato little-endian, con la pila creciendo hacia abajo.

  • Clasificación INTEGER: Hasta dos campos de 64 bits caben en registros.
  • Alineación: Los campos están alineados en límites de 8 bytes.
  • Límite: El tamaño total debe ser ≤16 bytes para devolución directa.

Estructuras grandes: argumento oculto en rdi

Las estructuras mayores de 16 bytes se clasifican como MEMORY. El llamador reserva memoria y pasa su dirección en rdi como un argumento oculto inicial.

Google AdInline article slot

Ejemplo: una estructura de 32 bytes:

struct many_nums {
    std::int64_t first{};
    std::int64_t second{};
    std::int64_t third{};
    std::int64_t fourth{};
};
many_nums construct_scary() {
    many_nums temp{10, 20, 30, 40};
    return temp;
}

En construct_scary:

construct_scary():
  mov QWORD PTR [rbp-8], rdi
  mov rax, QWORD PTR [rbp-8]
  mov QWORD PTR [rax], 10
  mov QWORD PTR [rax+8], 20
  mov QWORD PTR [rax+16], 30
  mov QWORD PTR [rax+24], 40
  ret

El repetido mov rax, [rbp-8] es un artefacto de -O0. En código optimizado, se usa rdi directamente.

Google AdInline article slot

En main:

main:
  sub rsp, 32
  lea rax, [rbp-32]
  mov rdi, rax
  call construct_scary
  ret
  • Reserva de memoria: Se realiza con sub rsp, size o en el montículo/pila.
  • Paso de dirección: La dirección se pasa en rdi antes de la llamada.
  • Clasificación MEMORY: Usada por la ABI System V para tipos >16 bytes o no-INTEGER.

Reglas de clasificación de valores devueltos

La ABI System V define tres categorías:

  • INTEGER: Hasta 16 bytes, campos escalares o agregados ≤2×64 bits → devueltos en rax/rdx.
  • MEMORY: >16 bytes, tipos SSE empaquetados >2×128 bits, o no-INTEGER → argumento oculto rdi.
  • SSE: Valores flotantes hasta 128 bits → devueltos en xmm0 (no cubierto aquí).

Compiladores como GCC y Clang generan código según estas reglas. Usa -O0 durante la depuración para ver claramente los pasos intermedios.

Puntos clave

  • Usa rax/rdx para devoluciones enteras hasta 16 bytes.
  • Para tipos clasificados como MEMORY, el llamador proporciona memoria mediante rdi.
  • La ABI System V garantiza comportamiento consistente entre Linux y macOS.
  • Las optimizaciones (-O2+) eliminan instrucciones mov innecesarias.
  • Verifica el código generado con objdump -d o Godbolt Compiler Explorer.

— Editorial Team

Advertisement 728x90

Leer después