Nuta
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować się zalogować lub zmienić katalog.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
W tym temacie opisano podstawowy interfejs binarny aplikacji (ABI) dla x64, rozszerzenie 64-bitowe do architektury x86. Obejmuje on tematy, takie jak konwencja wywoływania, układ typów, użycie stosu i rejestrów i inne.
Konwencje wywoływania x64
Istnieją dwie ważne różnice między x86 i x64:
- Możliwość adresowania 64-bitowego
- Szesnaście rejestrów 64-bitowych do użytku ogólnego.
Biorąc pod uwagę rozszerzony zestaw rejestrów, x64 używa __fastcall konwencji wywoływania i modelu obsługi wyjątków opartego na protokole RISC.
Konwencja __fastcall używa rejestrów dla pierwszych czterech argumentów, a ramka stosu przekazuje więcej argumentów. Aby uzyskać szczegółowe informacje na temat konwencji wywoływania x64, w tym użycie rejestrów, parametrów stosu, wartości zwracanych i odwijania stosu, zapoznaj się z konwencją wywoływania x64.
Aby uzyskać więcej informacji o __vectorcall konwencji wywoływania, zobacz __vectorcall.
Włączanie optymalizacji kompilatora x64
Poniższa opcja kompilatora pomaga zoptymalizować aplikację pod kątem architektury x64:
Typ x64 i układ magazynu
W tej sekcji opisano przechowywanie typów danych dla architektury x64.
Typy skalarne
Chociaż możliwy jest dostęp do danych z dowolnym wyrównaniem, wyrównaj dane na ich naturalnej granicy lub wielokrotności tej granicy, aby uniknąć utraty wydajności. Wyliczenia są stałymi wartościami i traktowane jako liczby całkowite o rozmiarze 32 bitów. W poniższej tabeli opisano definicję typu i zalecane przechowywanie danych, jak ma się to do wyrównania przy użyciu następujących wartości wyrównania.
- Bajt — 8 bitów
- Word — 16 bitów
- Doubleword – 32 bity
- Quadword — 64 bitów
- Octaword — 128 bitów
| Typ skalarny | Typ danych języka C | Rozmiar magazynu (w bajtach) | Zalecane wyrównanie |
|---|---|---|---|
INT8 |
char |
1 | Bajt |
UINT8 |
unsigned char |
1 | Bajt |
INT16 |
short |
2 | Słowo |
UINT16 |
unsigned short |
2 | Słowo |
INT32 |
int, long |
4 | Podwójne słowo |
UINT32 |
unsigned int, unsigned long |
4 | Podwójne słowo |
INT64 |
__int64 |
8 | Podwójne słowo |
UINT64 |
unsigned __int64 |
8 | Podwójne słowo |
FP32 (precyzja pojedyncza) |
float |
4 | Podwójne słowo |
FP64 (podwójna precyzja) |
double |
8 | Podwójne słowo |
POINTER |
* | 8 | Podwójne słowo |
__m64 |
struct __m64 |
8 | Podwójne słowo |
__m128 |
struct __m128 |
16 | Octaword |
Układ agregacji i unii x64
Inne typy, takie jak tablice, struktury i związki, mają bardziej rygorystyczne wymagania dotyczące wyrównania, które zapewniają spójne agregowanie i przechowywanie w unii oraz pobieranie danych. Poniżej przedstawiono definicje tablicy, struktury i unii:
Tablica
Zawiera uporządkowaną grupę sąsiednich obiektów danych. Każdy obiekt jest nazywany elementem. Wszystkie elementy w tablicy mają ten sam rozmiar i typ danych.
Struktura
Zawiera uporządkowaną grupę obiektów danych. W przeciwieństwie do elementów tablicy elementy członkowskie struktury mogą mieć różne typy danych i rozmiary.
Unia
Obiekt, który zawiera dowolny zestaw nazwanych elementów członkowskich. Elementy nazwanego zestawu mogą być dowolnego typu. Pamięć przydzielona dla unii (lub struktury) jest równa pamięci wymaganej dla największego elementu unii, oraz wszelkie wypełnienie potrzebne do wyrównania.
W poniższej tabeli przedstawiono zdecydowanie zalecane wyrównanie składowych skalarnych związków i struktur.
| Typ skalarny | Typ danych języka C | Wymagane wyrównanie |
|---|---|---|
INT8 |
char |
Bajt |
UINT8 |
unsigned char |
Bajt |
INT16 |
short |
Słowo |
UINT16 |
unsigned short |
Słowo |
INT32 |
int, long |
Podwójne słowo |
UINT32 |
unsigned int, unsigned long |
Podwójne słowo |
INT64 |
__int64 |
Podwójne słowo |
UINT64 |
unsigned __int64 |
Podwójne słowo |
FP32 (precyzja pojedyncza) |
float |
Podwójne słowo |
FP64 (podwójna precyzja) |
double |
Podwójne słowo |
POINTER |
* | Podwójne słowo |
__m64 |
struct __m64 |
Podwójne słowo |
__m128 |
struct __m128 |
Octaword |
Obowiązują następujące reguły wyrównania agregacji:
Wyrównanie tablicy jest takie samo jak wyrównanie jednego z elementów tablicy.
Wyrównanie początku struktury lub unii to maksymalne wyrównanie dowolnego z jej członków. Każdy członek w ramach struktury lub unii musi być umieszczony w odpowiednim wyrównaniu zgodnie z definicją z poprzedniej tabeli, co może wymagać ukrytego wypełnienia wewnętrznego, w zależności od poprzedniego członka.
Rozmiar struktury musi być całkowitą wielokrotnością jej wyrównania, co może wymagać wypełnienia po ostatnim elemencie. Ponieważ struktury i związki mogą być zgrupowane w tablicach, każdy element tablicy struktury lub unii musi zaczynać się i kończyć na odpowiednim wyrównaniu wcześniej określonym.
Istnieje możliwość dostosowania danych w taki sposób, aby przekraczać wymagania dotyczące wyrównania, pod warunkiem, że poprzednie zasady są zachowane.
Pojedynczy kompilator może dostosować pakowanie struktury ze względów rozmiaru. Na przykład /Zp (Wyrównanie członków struktury) umożliwia dostosowanie pakowania struktur.
Przykłady wyrównania struktury x64
Poniższe cztery przykłady deklarują wyrównaną strukturę lub unię, a odpowiadające im liczby ilustrują układ tej struktury lub unii w pamięci. Każda kolumna na rysunku reprezentuje bajt pamięci, a liczba w kolumnie wskazuje przemieszczenie tego bajtu. Nazwa w drugim wierszu każdego rysunku odpowiada nazwie zmiennej w deklaracji . Zacienione kolumny wskazują wypełnienie wymagane do osiągnięcia określonego wyrównania.
Przykład 1
// Total size = 2 bytes, alignment = 2 bytes (word).
_declspec(align(2)) struct {
short a; // +0; size = 2 bytes
}
Przykład 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
}
Na diagramie przedstawiono 24 bajty pamięci. Element int zajmuje bajty od 0 do 3. Diagram przedstawia wypełnienie dla bajtów od 4 do 7. Składowa b, podwójna, zajmuje bajty od 8 do 15. Członek c, typu short, zajmuje bajty od 16 do 17. Bajty od 18 do 23 są nieużywane.
Przykład 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
}
Na diagramie przedstawiono 12 bajtów pamięci. Element typu char zajmuje bajt 0. Wypełnianie bajtów 1. Element b, typu short, zajmuje bajty od 2 do 4. Element c, typu char, zajmuje bajt 4. Bajty od 5 do 7 to wypełnienie. Składowa d, typ danych int, zajmuje bajty od 8 do 11.
Przykład 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
}
Na diagramie przedstawiono 8 bajtów pamięci. Element p, znak znakowy, zajmuje bajt 0. Pole typu short zajmuje bajty od 0 do 1 włącznie. Członek l, typu long, zajmuje bajty od 0 do 3. Bajty od 4 do 7 są wypełnieniem.
Pola bitów
Pola bitów struktury są ograniczone do 64 bitów i mogą być typu int ze znakiem, int bez znaku, int64 lub int64 bez znaku. Pola bitowe, które przekraczają granicę typu, pomijają bity, aby wyrównać pole bitowe do następnego wyrównania typu. Na przykład pola bitowe liczb całkowitych mogą nie przekraczać granicy 32-bitowej.
Konflikty z kompilatorem x86
Typy danych, które są większe niż 4 bajty, nie są automatycznie wyrównane na stosie, gdy kompilujesz aplikację przy użyciu kompilatora x86. Ponieważ architektura kompilatora x86 jest stosem wyrównanym do 4 bajtów, cokolwiek większego niż 4 bajty, na przykład 64-bitowa liczba całkowita, nie może być automatycznie wyrównane do adresu 8-bajtowego.
Praca z danymi niewyrównanymi ma dwie implikacje.
Uzyskanie dostępu do niezsynchronizowanych lokalizacji może potrwać dłużej niż uzyskanie dostępu do wyrównanych lokalizacji.
Nie można używać nieprzystawionych lokalizacji w operacjach połączonych.
Jeśli potrzebujesz bardziej ścisłego wyrównania, użyj __declspec(align(N)) w deklaracjach zmiennych. To powoduje, że kompilator dynamicznie wyrównuje stos zgodnie z określonymi wymaganiami. Jednak dynamiczne dostosowywanie stosu w czasie wykonywania może spowodować wolniejsze wykonywanie aplikacji.
Użycie rejestrów x64
Architektura x64 zapewnia 16 rejestrów ogólnego przeznaczenia (określanych tutaj jako rejestry całkowite), a także 16 rejestrów XMM/YMM dostępnych do użytku zmiennoprzecinkowego. Rejestry lotne to rejestry tymczasowe, które są zakładane jako zmienione przez obiekt wywołujący podczas wywołania. Rejestry nieulotne są wymagane do zachowania ich wartości podczas wywołania funkcji i muszą zostać zapisane przez wywoływaną funkcję, jeśli są używane.
Rejestrowanie zmienności i ochrona
W poniższej tabeli opisano sposób użycia każdego rejestru między wywołaniami funkcji:
| Zarejestruj | Stan | Użycie |
|---|---|---|
| RAX | Zmienny | Rejestr wartości zwracanych |
| RCX | Zmienny | Pierwszy argument liczby całkowitej |
| RDX | Zmienny | Drugi argument liczby całkowitej |
| R8 | Zmienny | Trzeci argument liczby całkowitej |
| R9 | Zmienny | Czwarty argument liczby całkowitej |
| R10:R11 | Zmienny | Należy zachować zgodnie z potrzebami podmiotu wywołującego; używane w instrukcjach syscall/sysret |
| R12:R15 | Nieulotna | Musi być zachowane przez wykonawcę |
| RDI | Nieulotna | Musi być zachowane przez wykonawcę |
| RSI | Nieulotna | Musi być zachowane przez wykonawcę |
| RBX | Nieulotna | Musi być zachowane przez wykonawcę |
| RBP | Nieulotna | Może być używany jako wskaźnik ramki; muszą być zachowywane przez obiekt wywoływany |
| RSP | Nieulotna | Wskaźnik stosu |
| XMM0, YMM0 | Zmienny | Pierwszy argument FP; pierwszy argument typu wektorowego, gdy __vectorcall jest używany |
| XMM1, YMM1 | Zmienny | Drugi argument FP; drugi argument typu wektora, gdy __vectorcall jest używany |
| XMM2, YMM2 | Zmienny | Trzeci argument FP; trzeci argument typu wektorowego, gdy __vectorcall jest używany |
| XMM3, YMM3 | Zmienny | Czwarty argument FP; czwarty argument typu wektora, gdy __vectorcall jest używany |
| XMM4, YMM4 | Zmienny | Należy zachować według potrzeb przez wywołującego; piąty argument typu wektora, gdy __vectorcall jest używany |
| XMM5, YMM5 | Zmienny | Musi zostać zachowane zgodnie z potrzebami wywołującego; szósty argument typu wektorowego, gdy używany jest __vectorcall. |
| XMM6:XMM15, YMM6:YMM15 | Nieulotny (XMM), Ulotny (górna połowa YMM) | Należy zachować przez wywoływanie. Rejestry YMM muszą być zachowywane zgodnie z potrzebami wywołującego. |
Po zakończeniu działania funkcji i wpisie funkcji do wywołań biblioteki środowiska uruchomieniowego języka C i wywołaniach systemu Windows należy wyczyścić flagę kierunku w rejestrze flag procesora CPU.
Użycie stosu
Aby uzyskać szczegółowe informacje na temat alokacji stosu, wyrównania, typów funkcji i ramek stosu w architekturze x64, zobacz zastosowanie stosu x64.
Prolog i epilog
Każda funkcja, która przydziela przestrzeń stosu, wywołuje inne funkcje, zapisuje rejestry nieulotne lub używa obsługi wyjątków, musi mieć prolog, którego limity adresowe są opisane w danych rozwijania stosu skojarzonych z odpowiednim wpisem tabeli funkcji, oraz epilog przy każdym wyjściu z funkcji. Aby uzyskać szczegółowe informacje na temat wymaganego kodu prologu i epilogu na x64, zobacz x64 prolog i epilog.
Obsługa wyjątku x64
Aby uzyskać informacje na temat konwencji i struktur danych używanych do implementowania obsługi wyjątków strukturalnych i obsługi wyjątków języka C++ w architekturze x64, zobacz obsługa wyjątków x64.
Funkcje wewnętrzne i wbudowany zestaw
Jednym z ograniczeń kompilatora x64 jest brak wbudowanej obsługi asemblera. Oznacza to, że funkcje, których nie można zapisać w języku C lub C++, muszą być zapisywane jako podroutines lub jako funkcje wewnętrzne obsługiwane przez kompilator. Niektóre funkcje są wrażliwe na wydajność, a inne nie. Funkcje wrażliwe na wydajność powinny być implementowane jako funkcje wewnętrzne.
Funkcje wewnętrzne obsługiwane przez kompilator są opisane w temacie Funkcje wewnętrzne kompilatora.
Format obrazu x64
Format obrazu wykonywalnego x64 to PE32+. Obrazy wykonywalne (zarówno biblioteki DLL, jak i pliki EXE) są ograniczone do maksymalnego rozmiaru 2 gigabajtów, więc względne adresowanie z przemieszczeniem 32-bitowym może służyć do adresowania danych statycznych obrazów. Te dane obejmują tabelę adresów importu, stałe ciągów, statyczne dane globalne itd.