Delen via


Overzicht van x64 ABI-conventies

In dit onderwerp wordt de binaire interface van de basistoepassing (ABI) voor x64 beschreven, de 64-bits extensie voor de x86-architectuur. Het behandelt onderwerpen zoals de aanroepconventie, type indeling, en het gebruik van de stack en registers, en meer.

x64-aanroepconventies

Twee belangrijke verschillen tussen x86 en x64 zijn:

  • 64-bits adresseringsmogelijkheid
  • Zestien 64-bits registers voor algemeen gebruik.

Gezien de uitgebreide registratieset maakt x64 gebruik van de __fastcall aanroepende conventie en een op RISC gebaseerd uitzonderingsverwerkingsmodel.

De __fastcall conventie gebruikt registers voor de eerste vier argumenten en het stackframe om meer argumenten door te geven. Zie x64-oproepconventie voor meer informatie over de x64-aanroepconventie, waaronder registergebruik, stackparameters, retourwaarden en stack-afwikkeling.

Zie voor meer informatie over de __vectorcall oproepconventie __vectorcall.

Optimalisatie van x64-compiler inschakelen

Met de volgende compileroptie kunt u uw toepassing optimaliseren voor x64:

x64-type en opslagindeling

In deze sectie wordt de opslag van gegevenstypen voor de x64-architectuur beschreven.

Scalaire typen

Hoewel het mogelijk is om toegang te krijgen tot gegevens met elke uitlijning, kunt u gegevens uitlijnen op de natuurlijke grens, of een veelvoud van de natuurlijke grens, om prestatieverlies te voorkomen. Enums zijn constante gehele getallen en worden behandeld als 32-bits gehele getallen. In de volgende tabel worden de typedefinitie en aanbevolen opslag voor gegevens beschreven, omdat deze betrekking hebben op uitlijning met behulp van de volgende uitlijningswaarden:

  • Byte - 8 bits
  • Word - 16 bits
  • Dubbelwoord - 32 bits
  • Quadwoord - 64 bits
  • Octaword - 128 bits
Scalaire type C-gegevenstype Opslaggrootte (in bytes) Aanbevolen uitlijning
INT8 char 1 Byte
UINT8 unsigned char 1 Byte
INT16 short 2 Woord
UINT16 unsigned short 2 Woord
INT32 int, long 4 Dubbelwoord
UINT32 unsigned int, unsigned long 4 Dubbelwoord
INT64 __int64 8 Quadword
UINT64 unsigned __int64 8 Quadword
FP32 (enkele precisie) float 4 Dubbelwoord
FP64 (dubbele precisie) double 8 Quadword
POINTER * 8 Quadword
__m64 struct __m64 8 Quadword
__m128 struct __m128 16 Octaword

indeling x64 aggregaat en samenvoeging

Andere typen, zoals matrices, structs en samenvoegingen, hebben strengere uitlijningsvereisten die zorgen voor consistente opslag en samenvoeging en het ophalen van gegevens. Dit zijn de definities voor matrix, structuur en samenvoeging:

  • Array

    Bevat een geordende groep aangrenzende gegevensobjecten. Elk object wordt een element genoemd. Alle elementen in een matrix hebben dezelfde grootte en hetzelfde gegevenstype.

  • Structuur

    Bevat een geordende groep gegevensobjecten. In tegenstelling tot de elementen van een matrix kunnen de leden van een structuur verschillende gegevenstypen en -grootten hebben.

  • Unie

    Een object dat één van een set benoemde leden bevat. De leden van de benoemde set kunnen van elk type zijn. De opslag die is toegewezen voor een samenvoeging is gelijk aan de opslag die is vereist voor het grootste lid van die samenvoeging, plus eventuele opvullingen die vereist zijn voor uitlijning.

In de volgende tabel ziet u de sterk aanbevolen uitlijning voor de scalaire leden van vakbonden en structuren.

Scalaire type C-gegevenstype Vereiste uitlijning
INT8 char Byte
UINT8 unsigned char Byte
INT16 short Woord
UINT16 unsigned short Woord
INT32 int, long Dubbelwoord
UINT32 unsigned int, unsigned long Dubbelwoord
INT64 __int64 Quadword
UINT64 unsigned __int64 Quadword
FP32 (enkele precisie) float Dubbelwoord
FP64 (dubbele precisie) double Quadword
POINTER * Quadword
__m64 struct __m64 Quadword
__m128 struct __m128 Octaword

De volgende regels voor samenvoeging zijn van toepassing:

  • De uitlijning van een matrix is hetzelfde als de uitlijning van een van de elementen van de matrix.

  • De uitlijning van het begin van een structuur of een samenvoeging is de maximale uitlijning van elk afzonderlijk lid. Elk lid binnen de structuur of samenvoeging moet op de juiste uitlijning worden geplaatst, zoals gedefinieerd in de vorige tabel, waarvoor mogelijk impliciete interne opvulling vereist is, afhankelijk van het vorige lid.

  • De structuurgrootte moet een integraal veelvoud van de uitlijning zijn, wat na het laatste lid mogelijk opvulling vereist. Aangezien structuren en samenvoegingen kunnen worden gegroepeerd in matrices, moet elk matrixelement van een structuur of samenvoeging beginnen en eindigen op de juiste uitlijning die eerder is bepaald.

  • Het is mogelijk om gegevens op een zodanige manier uit te lijnen dat deze groter zijn dan de uitlijningsvereisten zolang de vorige regels worden gehandhaafd.

  • Een individuele compiler kan de verpakking van een structuur om grootteredenen aanpassen. Met /Zp (Struct Member Alignment) kan bijvoorbeeld de verpakking van structuren worden aangepast.

Voorbeelden van uitlijning van x64-structuur

De volgende vier voorbeelden declareren elk een uitgelijnde structuur of samenvoeging en de bijbehorende afbeeldingen illustreren de indeling van die structuur of samenvoeging in het geheugen. Elke kolom in een afbeelding vertegenwoordigt een byte geheugen en het getal in de kolom geeft de verplaatsing van die byte aan. De naam in de tweede rij van elke afbeelding komt overeen met de naam van een variabele in de declaratie. De gearceerde kolommen geven opvulling aan die nodig is om de opgegeven uitlijning te bereiken.

Voorbeeld 1

// Total size = 2 bytes, alignment = 2 bytes (word).

_declspec(align(2)) struct {
    short a;      // +0; size = 2 bytes
}

Diagram met de structuurindeling bijvoorbeeld 1. Het diagram toont 2 bytes geheugen. Lid a, een korte, neemt bytes 0 tot en met 1 in beslag.

Voorbeeld 2

// Total size = 24 bytes, alignment = 8 bytes (quadword).

_declspec(align(8)) struct {
    int a;       // +0; size = 4 bytes
    double b;    // +8; size = 8 bytes
    short c;     // +16; size = 2 bytes
}

Diagram met de structuurindeling bijvoorbeeld 2.

In het diagram ziet u 24 bytes geheugen. Lid a, een int, neemt bytes 0 tot en met 3 in beslag. Het diagram toont opvulling voor bytes 4 tot en met 7. Lid b, een double, neemt bytes 8 tot en met 15 in beslag. Element c, een short, beslaat bytes 16 tot en met 17. Bytes 18 tot en met 23 worden niet gebruikt.

Voorbeeld 3

// Total size = 12 bytes, alignment = 4 bytes (doubleword).

_declspec(align(4)) struct {
    char a;       // +0; size = 1 byte
    short b;      // +2; size = 2 bytes
    char c;       // +4; size = 1 byte
    int d;        // +8; size = 4 bytes
}

Diagram met de structuurindeling bijvoorbeeld 3.

In het diagram ziet u 12 bytes geheugen. Lid a, een character, bevindt zich op byte 0. Byte 1 is opvulling. Lid b, een korte, neemt bytes 2 tot en met 4 in beslag. Lid c, een karakter, bevindt zich in byte 4. Bytes 5 tot en met 7 zijn opvulling. Lid d, een int, neemt bytes 8 tot en met 11 in beslag.

Voorbeeld 4

// Total size = 8 bytes, alignment = 8 bytes (quadword).

_declspec(align(8)) union {
    char *p;      // +0; size = 8 bytes
    short s;      // +0; size = 2 bytes
    long l;       // +0; size = 4 bytes
}

Diagram met de unielayout voor voorbeeld 4.

In het diagram ziet u 8 bytes geheugen. Lid p, een karakter, neemt byte 0 in beslag. Lid s, een short, neemt bytes 0 tot en met 1 in beslag. Lid l, een long, neemt bytes 0 tot en met 3 in beslag. Bytes 4 tot en met 7 zijn opvulling.

Bitfields

Structuurbitvelden zijn beperkt tot 64 bits en kunnen van het type ondertekend, niet-ondertekende int, int64 of niet-ondertekende int64 zijn. Bitvelden die de typegrens overschrijden, slaan bits over om het bitveld uit te lijnen op de volgende typeuitlijning. Bitvelden met gehele getallen kunnen bijvoorbeeld geen 32-bits grens overschrijden.

Conflicten met de x86-compiler

Gegevenstypen die groter zijn dan 4 bytes, worden niet automatisch uitgelijnd op de stack wanneer u de x86-compiler gebruikt om een toepassing te compileren. Omdat de architectuur voor de x86-compiler een 4 byte uitgelijnde stack is, kan alles wat groter is dan 4 bytes, bijvoorbeeld een 64-bits geheel getal, niet automatisch worden uitgelijnd op een 8-byteadres.

Het werken met niet-uitgelijnde gegevens heeft twee gevolgen.

  • Het kan langer duren om niet-uitgelijnde locaties te openen dan nodig is voor toegang tot uitgelijnde locaties.

  • Niet-uitgelijnde locaties kunnen niet worden gebruikt in vergrendelde bewerkingen.

Als u striktere uitlijning nodig hebt, gebruikt u __declspec(align(N)) bij uw variabele declaraties. Dit zorgt ervoor dat de compiler de stack dynamisch uitlijnt om te voldoen aan uw specificaties. Het dynamisch aanpassen van de stack tijdens runtime kan echter leiden tot een tragere uitvoering van uw toepassing.

x64-gebruik registreren

De x64-architectuur biedt 16 registers voor algemeen gebruik (hierna geheel getalregisters genoemd) en 16 XMM/YMM-registers die beschikbaar zijn voor gebruik met drijvende komma. Vluchtige registers zijn scratchregisters die door de aanroeper worden verondersteld om gedurende een oproep te worden vernietigd. Niet-vluchtige registers zijn nodig om hun waarden te behouden bij een functieaanroep en moeten door de ontvanger van de oproep worden opgeslagen indien gebruikt.

Volatiliteit en behoud registreren

In de volgende tabel wordt beschreven hoe elk register wordt gebruikt voor functie-aanroepen:

Registreren Toestand Gebruik
RAX Vluchtig Register van retourwaarde
RCX Vluchtig Eerste geheel getalargument
RDX Vluchtig Tweede geheel getalargument
R8 Vluchtig Derde geheel getal argument
R9 Vluchtig Vierde geheelgetal-argument
R10:R11 Vluchtig Moet door de oproeper indien nodig worden bewaard; gebruikt in syscall/sysret-instructies
R12:R15 Niet-tevreden Moet bewaard blijven door de geroepen
RDI Niet-tevreden Moet bewaard blijven door de geroepen
RSI Niet-tevreden Moet bewaard blijven door de geroepen
RBX Niet-tevreden Moet bewaard blijven door de geroepen
RBP Niet-tevreden Kan worden gebruikt als framepointer; moet worden bewaard door de aangeroepene
RSP Niet-tevreden Stackpointer
XMM0, YMM0 Vluchtig Eerste FP-argument; eerste vector-type argument wanneer __vectorcall wordt gebruikt
XMM1, YMM1 Vluchtig Tweede FP-argument; tweede vector-type argument wanneer __vectorcall wordt gebruikt
XMM2, YMM2 Vluchtig Derde FP-argument; argument derde vectortype wanneer __vectorcall wordt gebruikt
XMM3, YMM3 Vluchtig Vierde FP-argument; vierde vectortype-argument wanneer __vectorcall wordt gebruikt
XMM4, YMM4 Vluchtig Moet bewaard blijven indien nodig door de beller; argument van het vijfde vectortype wanneer __vectorcall wordt gebruikt
XMM5, YMM5 Vluchtig Moet indien nodig door de aanroeper bewaard blijven; zesde vectortype-argument wanneer __vectorcall wordt gebruikt
XMM6:XMM15, YMM6:YMM15 Niet-vluchtig (XMM), Vluchtig (bovenste helft van YMM) Moet worden bewaard door de opgeroepene. YMM-registers moeten zo nodig worden bewaard door de beller.

Bij het afsluiten van een functie en aan het begin van een functie voor C Runtime Library-aanroepen en Windows-systeemoproepen wordt verwacht dat de richtingsvlag in het cpu-vlaggenregister is gewist.

Stackgebruik

Zie het gebruik van x64-stack voor meer informatie over stacktoewijzing, uitlijning, functietypen en stackframes op x64.

Prolog en epilog

Elke functie die stackruimte toewijst, andere functies aanroept, niet-compatibele registers opslaat of uitzonderingsafhandeling gebruikt, moet een prolog hebben waarvan de adreslimieten worden beschreven in de afwikkelgegevens die zijn gekoppeld aan de betreffende functietabelvermelding en epilogs bij elke uitgang naar een functie. Zie x64 prolog en epilog voor meer informatie over de vereiste prolog- en epilog-code op x64.

x64-uitzonderingsafhandeling

Zie x64-uitzonderingsafhandeling voor informatie over de conventies en gegevensstructuren die worden gebruikt voor het implementeren van gestructureerde uitzonderingsafhandeling en het gedrag van C++-uitzonderingsafhandeling op de x64.

Intrinsiek en inlineassembly

Een van de beperkingen voor de x64-compiler is geen inline assembly-ondersteuning. Dit betekent dat functies die niet kunnen worden geschreven in C of C++ moeten worden geschreven als subroutines of als intrinsieke functies die door de compiler worden ondersteund. Bepaalde functies zijn prestatiegevoelig, terwijl andere niet. Prestatiegevoelige functies moeten worden geïmplementeerd als intrinsieke functies.

De door de compiler ondersteunde intrinsieke functies worden beschreven in Compiler intrinsics.

x64-afbeeldingsindeling

De x64 uitvoerbare afbeeldingsindeling is PE32+. Uitvoerbare installatiekopieën (zowel DLL's als EXE's) zijn beperkt tot een maximale grootte van 2 gigabyte, dus relatieve adressering met een 32-bits verplaatsing kan worden gebruikt om statische afbeeldingsgegevens aan te pakken. Deze gegevens omvatten de importadrestabel, tekenreeksconstanten, statische globale gegevens, enzovoort.

Zie ook

oproepconventies