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