Wie x86-64 Werte mit der System V ABI zurückgibt
Betrachten wir den einfachen Fall der Rückgabe eines int:
one_plus_one():
mov DWORD PTR [rbp-4], 2
mov eax, DWORD PTR [rbp-4]
ret
Der Wert wird in eax (die unteren 32 Bit von rax) geladen. Ähnlich wird bei 64-Bit-Typen das gesamte Register rax verwendet.
Kleine Strukturen bis 16 Byte: Rückgabe über Register
Kleine Strukturen werden direkt in rax und rdx zurückgegeben. Betrachten wir eine 16-Byte-Struktur:
struct nums {
std::int64_t first{};
std::int64_t second{};
};
nums construct() {
nums ret{10, 120};
return ret;
}
Generierte Assemblercode:
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
Die Felder werden sequenziell angeordnet: first geht in rax, second in rdx. Die Speicheranordnung folgt little-endian, wobei der Stack nach unten wächst.
- Klassifizierung INTEGER: Bis zwei 64-Bit-Felder passen in die Register.
- Ausrichtung: Felder sind auf 8-Byte-Grenzen ausgerichtet.
- Einschränkung: Gesamtgröße muss ≤16 Byte betragen, um direkt zurückgegeben zu werden.
Große Strukturen: Versteckter Argument in rdi
Strukturen größer als 16 Byte werden als MEMORY klassifiziert. Der Aufrufer reserviert Speicher und übergibt dessen Adresse als verstecktes erstes Argument in rdi.
Beispiel: Eine 32-Byte-Struktur:
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;
}
In 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
Das wiederholte mov rax, [rbp-8] ist ein Artefakt von -O0. Optimierter Code nutzt rdi direkt.
In main:
main:
sub rsp, 32
lea rax, [rbp-32]
mov rdi, rax
call construct_scary
ret
- Speicherreservierung: Erfolgt via
sub rsp, sizeoder auf Heap/Stack. - Adressübergabe: Die Adresse wird vor dem Aufruf in
rdiübergeben. - Klassifizierung MEMORY: Wird von der System V ABI für Typen >16 Byte oder nicht-INTEGER-Typen verwendet.
Regeln zur Rückgabewert-Klassifizierung
Die System V ABI definiert drei Kategorien:
- INTEGER: Bis 16 Byte, skalare oder aggregierte Felder ≤2×64 Bit → Rückgabe in
rax/rdx. - MEMORY: >16 Byte, SSE-pakte Typen >2×128 Bit oder nicht-INTEGER → verstecktes
rdi-Argument. - SSE: Gleitkommawerte bis 128 Bit → Rückgabe in
xmm0(hier nicht behandelt).
Compiler wie GCC und Clang generieren Code basierend auf diesen Regeln. Verwende -O0 während der Fehlersuche, um Zwischenschritte klar zu sehen.
Wichtige Erkenntnisse
- Nutze
rax/rdxfür Ganzzahlrückgaben bis 16 Byte. - Für
MEMORY-klassifizierte Typen stellt der Aufrufer Speicher überrdibereit. - Die System V ABI sorgt für konsistentes Verhalten unter Linux und macOS.
- Optimierungen (
-O2+) eliminieren unnötigemov-Anweisungen. - Überprüfe den generierten Code mit
objdump -doder Godbolt Compiler Explorer.
— Editorial Team
Noch keine Kommentare.