Zurück zur Startseite

Rückgabe von x86-64-Strukturen: Register und MEMORY ABI

Der Artikel zerlegt die Mechanismen der Rückgabe grundlegender Typen und Strukturen aus Funktionen in x86-64 gemäß System V ABI. Beschreibt INTEGER- und MEMORY-Klassifizierungsregeln, Einsatz von rax/rdx-Registren und verstecktem rdi-Argument. Assembly-Beispiele ohne Optimierungen.

x86-64: Wie Strukturen über rax, rdx und rdi zurückgegeben werden
Advertisement 728x90

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:

Google AdInline article slot
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.

Google AdInline article slot

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.

Google AdInline article slot

In main:

main:
  sub rsp, 32
  lea rax, [rbp-32]
  mov rdi, rax
  call construct_scary
  ret
  • Speicherreservierung: Erfolgt via sub rsp, size oder 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/rdx für Ganzzahlrückgaben bis 16 Byte.
  • Für MEMORY-klassifizierte Typen stellt der Aufrufer Speicher über rdi bereit.
  • Die System V ABI sorgt für konsistentes Verhalten unter Linux und macOS.
  • Optimierungen (-O2+) eliminieren unnötige mov-Anweisungen.
  • Überprüfe den generierten Code mit objdump -d oder Godbolt Compiler Explorer.

— Editorial Team

Advertisement 728x90

Weiterlesen