Partager via


Vue d’ensemble des conventions ABI x64

Cette rubrique décrit l’interface ABI (Application Binary Interface) de base pour x64, l’extension 64 bits à l’architecture x86. Elle aborde des thèmes tels que la convention d’appel, la disposition de type, l’utilisation de piles et de registres, et plus encore.

Conventions d’appel x64

Les deux différences importantes entre x86 et x64 sont les suivantes :

  • Fonctionnalité d’adressage 64 bits
  • Seize registres 64 bits pour un usage général.

Avec le jeu de registres développé, x64 utilise la convention d’appel __fastcall et un modèle de gestion des exceptions basé sur RISC.

La convention __fastcall utilise des registres pour les quatre premiers arguments et le frame de pile pour passer d’autres arguments. Pour plus de détails sur la convention d’appel x64, notamment l’utilisation de registres, les paramètres de pile, les valeurs de retour et le déroulement de piles, consultez Convention d’appel x64.

Pour plus d’informations sur la convention d’appel __vectorcall, consultez __vectorcall.

Activer l’optimisation du compilateur x64

L’option de compilateur suivante vous aide à optimiser votre application pour x64 :

Disposition de types et de stockage x64

Cette section décrit le stockage des types de données pour l’architecture x64.

Types scalaires

Bien qu’il soit possible d’accéder aux données avec n’importe quel alignement, alignez les données sur leur limite naturelle ou un multiple de leur limite naturelle afin d’éviter toute perte de performances. Les énumérations sont des entiers constants et sont traitées comme des entiers 32 bits. Le tableau suivant décrit la définition des types et le stockage recommandé pour les données, lesquelles dépendent de l’alignement avec les valeurs d’alignement suivantes :

  • Octet - 8 bits
  • Mot - 16 bits
  • Double mot - 32 bits
  • Quadruple mot - 64 bits
  • Octuple mot - 128 bits
Type scalaire Type de données C Taille de stockage (en octets) Alignement recommandé
INT8 char 1 Byte
UINT8 unsigned char 1 Byte
INT16 short 2 Word
UINT16 unsigned short 2 Word
INT32 int, long 4 Double mot
UINT32 unsigned int, unsigned long 4 Double mot
INT64 __int64 8 Quadruple mot
UINT64 unsigned __int64 8 Quadruple mot
FP32 (simple précision) float 4 Double mot
FP64 (double précision) double 8 Quadruple mot
POINTER * 8 Quadruple mot
__m64 struct __m64 8 Quadruple mot
__m128 struct __m128 16 Octuple mot

Disposition des agrégations et des unions x64

D’autres types, tels que les tableaux, les structs et les unions, ont des critères d’alignement plus stricts qui garantissent une agrégation cohérente, le stockage d’unions et l’extraction de données. Voici les définitions des tableaux, des structures et des unions :

  • Tableau

    Contient un groupe ordonné d’objets de données adjacents. Chaque objet est appelé un élément. Tous les éléments d’un tableau ont la même taille et le même type de données.

  • Structure

    Contient un groupe ordonné d’objets de données. Contrairement aux éléments d’un tableau, les membres d’une structure peuvent avoir différents types et différentes tailles de données.

  • Union

    Objet qui contient l’un des membres d’un ensemble de membres nommés. Les membres du jeu nommé peuvent être de n’importe quel type. Le stockage alloué pour une union est égal au stockage requis pour le plus grand membre de cette union, ainsi que tout remplissage requis pour l’alignement.

Le tableau suivant présente l’alignement fortement recommandé pour les membres scalaires des unions et des structures.

Type scalaire Type de données C Alignement requis
INT8 char Byte
UINT8 unsigned char Byte
INT16 short Word
UINT16 unsigned short Word
INT32 int, long Double mot
UINT32 unsigned int, unsigned long Double mot
INT64 __int64 Quadruple mot
UINT64 unsigned __int64 Quadruple mot
FP32 (simple précision) float Double mot
FP64 (double précision) double Quadruple mot
POINTER * Quadruple mot
__m64 struct __m64 Quadruple mot
__m128 struct __m128 Octuple mot

Les règles d’alignement d’agrégation suivantes s’appliquent :

  • L’alignement d’un tableau est identique à l’alignement de l’un des éléments du tableau.

  • L’alignement du début d’une structure ou d’une union est l’alignement maximal de n’importe quel membre individuel. Chaque membre de la structure ou de l’union doit être placé à son alignement approprié tel que défini dans le tableau précédent, ce qui peut nécessiter un remplissage interne implicite en fonction du membre précédent.

  • La taille de la structure doit être un multiple intégral de son alignement, ce qui peut nécessiter un remplissage après le dernier membre. Étant donné que les structures et les unions peuvent être regroupées dans des tableaux, chaque élément de tableau d’une structure ou d’une union doit commencer et se terminer à l’alignement approprié précédemment déterminé.

  • Il est possible d’aligner les données de telle sorte qu’elles soient supérieures aux exigences d’alignement tant que les règles précédentes sont conservées.

  • Un compilateur individuel peut ajuster la compression d’une structure pour des raisons de taille. Par exemple, /Zp (alignement des membres de struct) permet d’ajuster la compression des structures.

Exemples d'alignement de structure x64

Les quatre exemples suivants déclarent chacun une structure ou une union alignée, et les figures correspondantes illustrent la disposition de cette structure ou union en mémoire. Chaque colonne d’une figure représente un octet de mémoire, et le chiffre dans chaque colonne indique le déplacement de cet octet. Le nom de la deuxième ligne de chaque figure correspond au nom d’une variable dans la déclaration. Les colonnes ombrées indiquent le remplissage requis pour atteindre l’alignement spécifié.

Exemple 1

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

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

Diagramme montrant la disposition de la structure de l’exemple 1.

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

Diagramme montrant la disposition de la structure de l’exemple 2.

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

Diagramme montrant la disposition de la structure de l’exemple 3.

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

Diagramme montrant la disposition de l’union de l’exemple 4.

Champs de bits

Les champs de bits de structure sont limités à 64 bits et peuvent être de type int signé, int non signé, int64 ou int64 non signé. Les champs de bits qui dépassent la limite de type ignorent les bits pour aligner le champ de bits sur l’alignement de type suivant. Par exemple, les champs de bits d’entier ne dépasseront pas une limite de 32 bits.

Conflits avec le compilateur x86

Les types de données dont la taille est supérieure à 4 octets ne sont pas alignés automatiquement sur la pile lorsque vous utilisez le compilateur x86 pour compiler une application. Étant donné que l’architecture du compilateur x86 est une pile alignée sur 4 octets, tout ce qui dépasse 4 octets, par exemple un entier 64 bits, ne peut pas être aligné automatiquement sur une adresse de 8 octets.

L’utilisation de données non alignées a deux implications.

  • L’accès aux emplacements non alignés peut prendre plus de temps que l’accès aux emplacements alignés.

  • Les emplacements non alignés ne peuvent pas être utilisés dans les opérations verrouillées.

Si vous avez besoin d’un alignement plus strict, utilisez __declspec(align(N)) sur vos déclarations de variable. Ainsi, le compilateur aligne dynamiquement la pile pour répondre à vos spécifications. Toutefois, l’ajustement dynamique de la pile au moment de l’exécution peut entraîner une exécution plus lente de votre application.

Utilisation des registres x64

L’architecture x64 fournit 16 registres à usage général (ci-après dénommés « registres d’entiers ») en plus des 16 registres XMM/YMM disponibles pour l’utilisation de nombres à virgule flottante. Les registres volatils sont des registres de travail censés être détruits après un appel. Les registres non volatils doivent conserver leurs valeurs tout au long d'un appel de fonction et être enregistrés par l'appelé s'il les utilise.

Volatilité et conservation des registres

Le tableau suivant explique comment chaque registre est utilisé dans les appels de fonction :

Inscrire État Utiliser
RAX Volatil Registre des valeurs de retour
RCX Volatil Premier argument entier
RDX Volatil Deuxième argument entier
R8 Volatil Troisième argument entier
R9 Volatil Quatrième argument entier
R10:R11 Volatil Doit être conservé si nécessaire par l'appelant ; utilisé dans les instructions syscall/sysret
R12:R15 Non volatil Doit être conservé par l'appelé
RDI Non volatil Doit être conservé par l'appelé
RSI Non volatil Doit être conservé par l'appelé
RBX Non volatil Doit être conservé par l'appelé
RBP Non volatil Peut être utilisé comme pointeur de frame ; doit être conservé par l'appelé
RSP Non volatil Pointeur de pile
XMM0, YMM0 Volatil Premier argument FP ; premier argument de type vectoriel quand __vectorcall est utilisé
XMM1, YMM1 Volatil Deuxième argument FP ; deuxième argument de type vectoriel quand __vectorcall est utilisé
XMM2, YMM2 Volatil Troisième argument FP ; troisième argument de type vectoriel quand __vectorcall est utilisé
XMM3, YMM3 Volatil Quatrième argument FP ; quatrième argument de type vectoriel quand __vectorcall est utilisé
XMM4, YMM4 Volatil Doit être conservé si nécessaire par l’appelant ; cinquième argument de type vectoriel quand __vectorcall est utilisé
XMM5, YMM5 Volatil Doit être conservé si nécessaire par l'appelant ; sixième argument de type vectoriel quand __vectorcall est utilisé
XMM6:XMM15, YMM6:YMM15 Non volatil (XMM), volatil (moitié supérieure de YMM) Doit être conservé par l’appelé. Les registres YMM doivent être conservés si nécessaire par l'appelant.

Lors de la sortie de fonction et de l’entrée de fonction sur les appels de la bibliothèque C Runtime et les appels système Windows, l’indicateur de direction dans le registre des indicateurs de processeur est censé être supprimé.

Utilisation de la pile

Pour plus d’informations sur l’allocation de piles, l’alignement, les types de fonctions et les trames de pile sur x64, consultez Utilisation d’une pile x64.

Prologue et épilogue

Chaque fonction qui alloue de l’espace de pile, appelle d’autres fonctions, enregistre des registres non volatils ou utilise une gestion des exceptions doit avoir un prologue dont les limites d’adresse sont décrites dans les données de déroulement associées à l’entrée de table de fonctions respectives, et des épilogues à chaque sortie d’une fonction. Pour plus d’informations sur le code de prologue et d’épilogue requis sur x64, consultez Prologue et épilogue x64.

Gestion d’exceptions x64

Pour plus d’informations sur les conventions et les structures de données utilisées pour implémenter la gestion des exceptions structurées et le comportement de gestion des exceptions C++ sur x64, consultez Gestion des exceptions x64.

Assembly de fonctions intrinsèques et inline

L’une des contraintes du compilateur x64 est que l’assembleur inline n’est pas pris en charge. Cela signifie que les fonctions qui ne peuvent pas être écrites en C ou C++ doivent être écrites en tant que sous-routines ou fonctions intrinsèques prises en charge par le compilateur. Certaines fonctions sont sensibles aux performances, tandis que d’autres non. Les fonctions sensibles aux performances doivent être implémentées en tant que fonctions intrinsèques.

Les fonctions intrinsèques prises en charge par le compilateur sont décrites dans Fonctions intrinsèques du compilateur.

Format d’image x64

Le format d’image exécutable x64 est PE32+. Les images exécutables (DLL et EXE) sont limitées à une taille maximale de 2 gigaoctets, donc l’adressage relatif avec un déplacement de 32 bits peut être utilisé pour traiter les données d’image statiques. Ces données comprennent la table IAT (Import Address Table), les constantes de chaîne, les données globales statiques et ainsi de suite.

Voir aussi

Conventions d’appel