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:
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.
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.
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, sizeo en el montículo/pila. - Paso de dirección: La dirección se pasa en
rdiantes 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/rdxpara devoluciones enteras hasta 16 bytes. - Para tipos clasificados como
MEMORY, el llamador proporciona memoria medianterdi. - La ABI System V garantiza comportamiento consistente entre Linux y macOS.
- Las optimizaciones (
-O2+) eliminan instruccionesmovinnecesarias. - Verifica el código generado con
objdump -do Godbolt Compiler Explorer.
— Editorial Team
Aún no hay comentarios.