x64 ABI 慣例概觀
本主題描述 x64 的基本應用程式二進位介面 (ABI),亦即 x86 結構的 64 位元延伸模組。 其中涵蓋呼叫慣例、類型配置、堆疊和註冊使用方式等主題。
x64 呼叫慣例
x86 和 x64 之間的兩個重要差異如下:
- 64 位元定址功能
- 一般用途的 16 個 64 位元暫存器。
假設有展開的暫存器集,x64 會使用 __fastcall 呼叫慣例和 RISC 型例外狀況處理模型。
__fastcall
慣例會針對前四個引數使用暫存器,並使用堆疊框架來傳遞更多引數。 如需有關 x64 呼叫慣例的詳細資訊,包括註冊使用方式、堆疊參數、傳回值和堆疊回溯,請參閱 x64 呼叫慣例 (部分機器翻譯)。
如需 __vectorcall
呼叫慣例的詳細資訊,請參閱 __vectorcall
(部分機器翻譯)。
啟用 x64 編譯器最佳化
下列編譯器選項可協助您將 x64 的應用程式最佳化:
x64 類型和儲存配置
本節說明 x64 結構資料類型的儲存。
純量類型
儘管可以透過任何對齊方式存取資料,請在其自然界限或自然界限倍數上對齊資料,以避免減損效能。 列舉是常數整數,並被視為 32 位元整數。 下表描述類型定義和建議的資料儲存方式,因為它與使用下列對齊值進行對齊有關:
- 位元組 - 8 位元
- 字組 - 16 位元
- 雙倍字組 - 32 位元
- 四倍字組 - 64 位元
- 八倍字組 - 128 位元
純量類型 | C 資料類型 | 儲存大小 (以位元組為單位) | 建議的對齊方式 |
---|---|---|---|
INT8 |
char |
1 | Byte |
UINT8 |
unsigned char |
1 | Byte |
INT16 |
short |
2 | Word |
UINT16 |
unsigned short |
2 | Word |
INT32 |
% | 4 | 雙倍字組 |
UINT32 |
% | 4 | 雙倍字組 |
INT64 |
__int64 |
8 | 四倍字組 |
UINT64 |
unsigned __int64 |
8 | 四倍字組 |
FP32 (單精確度) |
float |
4 | 雙倍字組 |
FP64 (雙精確度) |
double |
8 | 四倍字組 |
POINTER |
* | 8 | 四倍字組 |
__m64 |
struct __m64 |
8 | 四倍字組 |
__m128 |
struct __m128 |
16 | 八倍字組 |
x64 彙總和聯合配置
其他類型 (例如陣列、結構和聯合) 具有更嚴格的對齊需求,可確保一致的彙總和聯合儲存以及資料擷取。 以下是陣列、結構和聯合的定義:
陣列
包含相鄰資料物件的已排序群組。 每個物件稱為「元素」。 陣列中的所有元素具有相同的大小和資料類型。
結構
包含資料物件的已排序群組。 不同於陣列的元素,結構的成員可以有不同的資料類型和大小。
Union
保留具名成員集之任一成員的物件。 具名集的成員可以是任何類型。 配置給聯合的儲存體等於該聯合最大成員所需的儲存體,再加上對齊所需的任何填補。
下表顯示針對聯合和結構的純量成員強烈建議的對齊方式。
純量類型 | C 資料類型 | 必要的對齊方式 |
---|---|---|
INT8 |
char |
Byte |
UINT8 |
unsigned char |
位元組 |
INT16 |
short |
Word |
UINT16 |
unsigned short |
Word |
INT32 |
% | 雙倍字組 |
UINT32 |
% | 雙倍字組 |
INT64 |
__int64 |
四倍字組 |
UINT64 |
unsigned __int64 |
四倍字組 |
FP32 (單精確度) |
float |
雙倍字組 |
FP64 (雙精確度) |
double |
四倍字組 |
POINTER |
* | 四倍字組 |
__m64 |
struct __m64 |
四倍字組 |
__m128 |
struct __m128 |
八倍字組 |
下列彙總對齊規則適用:
陣列的對齊方式與陣列其中一個元素的對齊方式相同。
結構或聯合的開頭對齊方式是任何個別成員的最大對齊方式。 結構或聯合內的每個成員都必須按照上表定義的正確對齊方式放置,這可能需要隱含的內部填補,視前一個成員而定。
結構大小必須是其對齊的整數倍數,可能需要在最後一個成員之後填補。 由於結構和聯合可以分組為陣列,因此結構或聯合的每個陣列元素都必須以先前確定的正確對齊方式開始和結束。
只要保留先前的規則,就可以使用大於對齊需求的方式來對齊資料。
個別編譯器可能會基於大小原因調整結構的封裝。 例如,/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 堆疊使用方式 (部分機器翻譯)。
初構和終解
每個配置堆疊空間、呼叫其他函式、儲存非揮發性暫存器或使用例外狀況處理的函式,都必須有一個其位址限制描述在個別函式資料表項目相關聯之回溯資料中的初構,以及在函式每個結束處的終解。 如需有關 x64 上所需之初構和終解的詳細資訊,請參閱 x64 初構和終解 (部分機器翻譯)。
x64 例外狀況處理
如需有關 x64 上用來實作結構化例外狀況處理和 C++ 例外狀況處理行為之慣例和資料結構的詳細資訊,請參閱 x64 例外狀況處理 (部分機器翻譯)。
內建和內嵌組譯碼
x64 編譯器的其中一個條件約束是沒有內嵌組譯工具支援。 這表示無法以 C 或 C++ 撰寫的函式必須撰寫為副程式或編譯器支援的內建函式。 某些函式會區分效能,某些函式則不區分效能。 會區分效能的函式應實作為內建函式。
如需了解編譯器支援的內建函式,請參閱編譯器內建函式中的描述 (部分機器翻譯)。
x64 映像格式
x64 可執行映像格式為 PE32+。 可執行檔映像 (DLL 和 EXE) 的大小上限為 2 GB,因此使用 32 位元位移的相對定址可用來處理靜態映像資料。 此資料包括匯入位址資料表、字串常數、靜態全域資料等等。