Zwracanie wartości z funkcji w x86-64 według System V ABI
Kompilator x86-64 zgodny z System V ABI wykorzystuje rejestry rax i rdx do zwracania pierwszych dwóch wartości typu integer. Dla struktur stosowane są zasady klasyfikacji: INTEGER dla małych rozmiarów, MEMORY dla dużych. Oznacza to, czy wartość jest przekazywana bezpośrednio czy przez ukryty wskaźnik w rejestrze rdi.
Rozważmy podstawowy przypadek zwracania int:
one_plus_one():
mov DWORD PTR [rbp-4], 2
mov eax, DWORD PTR [rbp-4]
ret
Wartość jest ładowana do eax (dolnych 32 bitów rax). Podobnie dla typów 64-bitowych używany jest pełen rax.
Struktury do 16 bajtów: zwracanie przez rejestry
Małe struktury umieszczane są w rax i rdx. Przykład struktury o rozmiarze 16 bajtów:
struct nums {
std::int64_t first{};
std::int64_t second{};
};
nums construct() {
nums ret{10, 120};
return ret;
}
Wygenerowany kod:
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
Pola są rozprowadzane kolejno: first w rax, second w rdx. Kolejność w pamięci odpowiada little-endian, a stos rośnie w dół.
- Klasyfikacja
INTEGER: Do dwóch pól 64-bitowych można umieścić dane w rejestrach. - Wyrównanie: Pola są wyrównane do 8 bajtów.
- Ograniczenie: Łączny rozmiar ≤ 16 bajtów dla bezpośredniego zwracania.
Duże struktury: ukryty argument w rdi
Struktury >16 bajtów klasyfikowane są jako MEMORY. Kod wywołujący alokuje pamięć i przekazuje adres w rdi jako pierwszy (ukryty) argument.
Przykład struktury 32 bajtów:
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;
}
W 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
Powtarzające się mov rax, [rbp-8] — artefakt -O0. W kodzie zoptymalizowanym używa się rdi bezpośrednio.
W main:
main:
sub rsp, 32
lea rax, [rbp-32]
mov rdi, rax
call construct_scary
ret
- Alokacja pamięci:
sub rsp, sizelub na stosie/heapie. - Przekazanie adresu: W
rdiprzed wywołaniem. - Klasyfikacja
MEMORY: Według System V ABI dla typów >16 bajtów lub nie-INTEGER.
Zasady klasyfikacji zwracanych wartości
System V ABI definiuje klasy:
- INTEGER: Do 16 bajtów, skalarne lub agregowane pola ≤2×64 bitów →
rax/rdx. - MEMORY: >16 bajtów, zbiory SSE >2×128 bitów, nie-INTEGER → ukryty
rdi. - SSE: Liczby zmiennoprzecinkowe do 128 bitów →
xmm0(nie omawiane tutaj).
Kompilator (GCC/Clang) generuje kod zgodnie z tymi zasadami. Do debugowania użyj -O0, aby zobaczyć kroki pośrednie.
Co ważne
- Rejestry
rax/rdxdo zwracania wartości integer ≤16 bajtów. - Ukryty argument
rdidla klasyMEMORY: wywołujący alokuje pamięć. - System V ABI jednolity dla Linuxa i macOS.
- Optymalizacje (
-O2+) minimalizują zbędnemov. - Sprawdzaj za pomocą
objdump -dlub Godbolt do weryfikacji.
— Editorial Team
Brak komentarzy.