Zpět na domů

Návrat struktur x86-64: registry a MEMORY ABI

Článek rozebírá mechanismy návratu fundamentálních typů a struktur z funkcí v x86-64 podle System V ABI. Jsou popsána pravidla klasifikace INTEGER a MEMORY, použití registrů rax/rdx a skrytého argumentu rdi. Uvedeny příklady assembleru bez optimalizací.

x86-64: jak vracet struktury přes rax, rdx a rdi
Advertisement 728x90

Jak funkce vrací hodnoty v x86-64 podle System V ABI

Kompilátor x86-64 podle System V ABI používá registry rax a rdx pro návrat prvních dvou hodnot typu integer. Pro struktury platí pravidla klasifikace: INTEGER pro malé velikosti, MEMORY pro velké. To určuje, zda se hodnota předá přímo nebo prostřednictvím skrytého ukazatele v rdi.

Zvažme základní případ návratu int:

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

Hodnota je načtena do eax (nižších 32 bitů rax). Podobně pro 64bitové typy se používá celý registr rax.

Google AdInline article slot

Malé struktury do 16 bajtů: návrat přes registry

Malé struktury se umísťují do rax a rdx. Příklad struktury o velikosti 16 bajtů:

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

Vygenerovaný kód:

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

Pole jsou rozdělena postupně: first do rax, second do rdx. Pořadí v paměti odpovídá little-endian s klesajícím směrem zásobníku.

Google AdInline article slot
  • Klasifikace INTEGER: Až dva 64bitové pole se umístí do registrů.
  • Zarovnání: Pole jsou zarovnána po 8 bajtech.
  • Omezení: Celková velikost ≤ 16 bajtů pro přímý návrat.

Velké struktury: skrytý argument v rdi

Struktury větší než 16 bajtů jsou klasifikovány jako MEMORY. Volající kód vyhradí paměť a předá adresu v rdi jako první (skrytý) argument.

Příklad struktury o velikosti 32 bajtů:

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;
}

Ve funkci construct_scary:

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

Opakované mov rax, [rbp-8] jsou artefakt -O0. Optimalizovaný kód používá rdi přímo.

Ve main:

main:
  sub rsp, 32
  lea rax, [rbp-32]
  mov rdi, rax
  call construct_scary
  ret
  • Vyhrazení paměti: sub rsp, size nebo na zásobníku/paměti dynamické.
  • Předání adresy: Do rdi před voláním.
  • Klasifikace MEMORY: Podle System V ABI pro typy >16 bajtů nebo ne-INTEGER.

Pravidla klasifikace návratových hodnot

System V ABI definuje třídy:

  • INTEGER: Do 16 bajtů, skalární nebo agregované pole ≤2×64 bitů → rax/rdx.
  • MEMORY: >16 bajtů, SSE-zabaleno >2×128 bitů, ne-INTEGER → skrytý rdi.
  • SSE: Floating-point do 128 bitů → xmm0 (zde nepopsáno).

Kompilátor (GCC/Clang) generuje kód podle těchto pravidel. Pro ladění použijte -O0 pro viditelné mezivýsledky.

Co je důležité

  • Registry rax/rdx pro návrat hodnot typu integer ≤16 bajtů.
  • Skrytý argument rdi pro třídu MEMORY: volající vyhradí paměť.
  • System V ABI standardizuje chování na Linuxu/macOS.
  • Optimalizace (-O2+) minimalizují nadbytečné mov.
  • Ověřte pomocí objdump -d nebo Godbolt.

— Editorial Team

Advertisement 728x90

Číst dál