x64 ABI 慣例概觀
本主題描述 x64 的基本應用程式二進位介面 (ABI),這是 x86 架構的 64 位擴充功能。 其中涵蓋呼叫慣例、類型配置、堆疊和註冊使用等主題。
x64 呼叫慣例
x86 和 x64 之間的兩個重要差異如下:
- 64 位定址功能
- 164 位暫存器用於一般用途。
假設展開的暫存器集,x64 會使用 __fastcall 呼叫慣例和以 RISC 為基礎的例外狀況處理模型。
慣例 __fastcall
會使用前四個引數的暫存器,以及堆疊框架來傳遞更多引數。 如需 x64 呼叫慣例的詳細資訊,包括註冊使用方式、堆疊參數、傳回值和堆疊回溯,請參閱 x64 呼叫慣例 。
啟用 x64 編譯器優化
下列編譯器選項可協助您優化 x64 的應用程式:
x64 類型和儲存配置
本節說明 x64 架構的資料類型儲存。
純量類型
雖然可以存取具有任何對齊方式的資料、對齊其自然界限上的資料或其自然界限的倍數,以避免效能遺失。 列舉是常數整數,會被視為 32 位整數。 下表描述資料的類型定義和建議儲存體,因為它與使用下列對齊值進行對齊有關:
- 位元組 - 8 位
- Word - 16 位
- Doubleword - 32 位
- 四字 - 64 位
- Octaword - 128 位
純量類型 | C 資料類型 | 儲存體大小 (以位元組為單位) | 建議的對齊方式 |
---|---|---|---|
INT8 |
char |
1 | Byte |
UINT8 |
unsigned char |
1 | Byte |
INT16 |
short |
2 | Word |
UINT16 |
unsigned short |
2 | Word |
INT32 |
int , long |
4 | Doubleword |
UINT32 |
unsigned int , unsigned long |
4 | Doubleword |
INT64 |
__int64 |
8 | 四字 |
UINT64 |
unsigned __int64 |
8 | 四字 |
FP32 (單精確度) |
float |
4 | Doubleword |
FP64 (雙精確度) |
double |
8 | 四字 |
POINTER |
* | 8 | 四字 |
__m64 |
struct __m64 |
8 | 四字 |
__m128 |
struct __m128 |
16 | Octaword |
x64 匯總和等位配置
其他類型,例如陣列、結構及等位,具有更嚴格的對齊需求,可確保一致的匯總和等位儲存和資料擷取。 以下是陣列、結構和等位的定義:
陣列
包含相鄰資料物件的已排序群組。 每個物件都稱為 元素 。 陣列中的所有元素具有相同的大小和資料類型。
結構
包含已排序的資料物件群組。 不同于陣列的元素,結構的成員可以有不同的資料類型和大小。
Union
物件,保留一組具名成員的任何一個。 具名集的成員可以是任何類型。 配置給等位的儲存體等於該聯集最大成員所需的儲存體,再加上對齊所需的任何填補。
下表顯示聯合和結構的純量成員強烈建議對齊。
純量類型 | C 資料類型 | 必要的對齊方式 |
---|---|---|
INT8 |
char |
Byte |
UINT8 |
unsigned char |
Byte |
INT16 |
short |
Word |
UINT16 |
unsigned short |
Word |
INT32 |
int , long |
Doubleword |
UINT32 |
unsigned int , unsigned long |
Doubleword |
INT64 |
__int64 |
四字 |
UINT64 |
unsigned __int64 |
四字 |
FP32 (單精確度) |
float |
Doubleword |
FP64 (雙精確度) |
double |
四字 |
POINTER |
* | 四字 |
__m64 |
struct __m64 |
四字 |
__m128 |
struct __m128 |
Octaword |
套用下列匯總對齊規則:
陣列的對齊方式與陣列其中一個專案的對齊方式相同。
結構開頭或等位的對齊方式是任何個別成員的最大對齊方式。 結構或等位內的每個成員都必須依上表所定義的正確對齊方式放置,這可能需要隱含的內部填補,視前一個成員而定。
結構大小必須是其對齊的整數倍數,可能需要在最後一個成員之後填補。 因為結構和等位可以分組在陣列中,因此結構或等位的每個陣列元素都必須以先前判斷的適當對齊方式開始和結束。
只要維護先前的規則,就可以以比對齊需求更大的方式來對齊資料。
個別編譯器可能會因大小原因調整結構的封裝。 例如, /Zp(結構成員對齊) 允許調整結構的封裝。
x64 結構對齊範例
下列四個範例分別宣告對齊的結構或等位,而對應的圖表說明記憶體中該結構或等位的配置。 圖中的每個資料行都代表記憶體的位元組,而資料行中的數位表示該位元組的位移。 每個圖第二個數據列中的名稱會對應至宣告中的變數名稱。 陰影資料行表示達到指定對齊所需的邊框間距。
範例 1
// Total size = 2 bytes, alignment = 2 bytes (word).
_declspec(align(2)) struct {
short a; // +0; size = 2 bytes
}
範例 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
}
範例 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
}
範例 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
}
位元欄位
結構位欄位限制為 64 位,且類型為signed int、unsigned int、int64 或 unsigned int64。 跨類型界限的位欄位將會略過位,以將位欄位對齊下一個類型對齊。 例如,整數位欄位可能不會跨越 32 位界限。
與 x86 編譯器衝突
當您使用 x86 編譯器編譯應用程式時,大於 4 個位元組的資料類型不會在堆疊上自動對齊。 因為 x86 編譯器的架構是一個 4 位元組對齊的堆疊,因此任何大於 4 個位元組的任何專案,例如 64 位整數,都無法自動對齊 8 位元組位址。
使用未對齊的資料有兩個含意。
存取未對齊的位置可能需要較長的時間,而不是存取對齊的位置。
未對齊的位置無法用於聯結作業。
如果您需要更嚴格的對齊方式,請在 __declspec(align(N))
變數宣告上使用 。 這會導致編譯器以動態方式對齊堆疊以符合您的規格。 不過,在執行時間動態調整堆疊可能會導致應用程式執行速度變慢。
x64 註冊使用方式
x64 架構提供 16 個一般用途暫存器(以下簡稱整數暫存器),以及 16 個可用於浮點使用的 XMM/YMM 暫存器。 動態暫存器是臨時暫存器,由呼叫者假設為跨呼叫終結。 需要靜態暫存器才能跨函式呼叫保留其值,且靜態暫存器必須由被呼叫者 (如果使用的話) 儲存。
暫存器波動性與保留
下表描述如何跨函式呼叫使用每一個暫存器:
註冊 | 狀態 | 使用 |
---|---|---|
RAX | 動態 | 傳回值暫存器 |
RCX | 動態 | 第一個整數引數 |
RDX | 動態 | 第二個整數引數 |
R8 | 動態 | 第三個整數引數 |
R9 | 動態 | 第四個整數引數 |
R10:R11 | 動態 | 必須由呼叫者視需要保留;在 syscall/sysret 指令中使用 |
R12:R15 | 靜態 | 必須由被呼叫者保留 |
RDI | 靜態 | 必須由被呼叫者保留 |
RSI | 靜態 | 必須由被呼叫者保留 |
RBX | 靜態 | 必須由被呼叫者保留 |
RBP | 靜態 | 必須用作框架指標;必須由被呼叫者保留 |
RSP | 靜態 | 堆疊指標 |
XMM0, YMM0 | 動態 | 第一個 FP 引數;使用 __vectorcall 時的第一個向量型別引數 |
XMM1, YMM1 | 動態 | 第二個 FP 引數;使用 __vectorcall 時的第二個向量類型引數 |
XMM2, YMM2 | 動態 | 第三個 FP 引數;使用 __vectorcall 時的第三個向量類型引數 |
XMM3, YMM3 | 動態 | 第四個 FP 引數;使用 __vectorcall 時的第四個向量類型引數 |
XMM4, YMM4 | 動態 | 必須由呼叫者視需要保留;使用 __vectorcall 時的第五個向量型別引數 |
XMM5, YMM5 | 動態 | 必須由呼叫者視需要保留;使用 __vectorcall 時的第六個向量型別引數 |
XMM6:XMM15, YMM6:YMM15 | 靜態 (XMM)、動態 (YMM 的上半部分) | 必須由被呼叫者保留。 YMM 暫存器必須由被呼叫者視需要保留。 |
在函式結束和 C 執行時間程式庫呼叫和 Windows 系統呼叫的函式專案上,應該會清除 CPU 旗標暫存器中的方向旗標。
堆疊使用量
如需 x64 上的堆疊配置、對齊方式、函式類型和堆疊框架的詳細資訊,請參閱 x64 堆疊使用量 。
Prolog 和 epilog
配置堆疊空間、呼叫其他函式、儲存非揮發性暫存器或使用例外狀況處理的每個函式,都必須有一個 prolog,其位址限制會在與個別函式資料表專案相關聯的回溯資料中描述,以及每個函式結尾的結尾。 如需 x64 上所需初構和表碼的詳細資訊,請參閱 x64 初構和表結 。
x64 例外狀況處理
如需在 x64 上實作結構化例外狀況處理和 C++ 例外狀況處理行為的慣例和資料結構的相關資訊,請參閱 x64 例外狀況處理 。
內建和內嵌元件
x64 編譯器的其中一個條件約束不是內嵌組譯工具支援。 這表示不能以 C 或 C++ 撰寫的函式,必須以副程式撰寫,或撰寫為編譯器支援的內建函式。 某些函式會區分效能,而其他函式則不具效能。 效能敏感函式應實作為內建函式。
編譯器支援的內建函式會在編譯器內建函式 中 描述。
x64 影像格式
x64 可執行影像格式為 PE32+。 可執行檔映射 (DLL 和 EXE) 的大小上限為 2 GB,因此使用 32 位位移位的相對定址可用來處理靜態影像資料。 此資料包括匯入位址資料表、字串常數、靜態全域資料等等。
另請參閱
意見反應
https://aka.ms/ContentUserFeedback。
即將登場:在 2024 年,我們將逐步淘汰 GitHub 問題作為內容的意見反應機制,並將它取代為新的意見反應系統。 如需詳細資訊,請參閱:提交並檢視相關的意見反應