Общие сведения о соглашениях ABI x64
В этом разделе описывается базовый двоичный интерфейс приложения (ABI) для x64, 64-разрядное расширение архитектуры x86. В ней рассматриваются такие темы, как соглашение о вызовах, макет типов, стек и регистрация использования и многое другое.
Соглашения о вызовах x64
Два важных различия между x86 и x64:
- 64-разрядная возможность адресации
- Шестнадцать 64-разрядных регистров для общего использования.
Учитывая расширенный набор регистров, архитектура x64 использует соглашение о вызовах __fastcall и модель обработки исключений на основе RISC.
Соглашение __fastcall
использует регистры для первых четырех аргументов и кадр стека для передачи дополнительных аргументов. Подробные сведения о соглашении о вызовах x64, в том числе об использовании регистров, параметрах стека, возвращаемых значениях и раскрутке стека, см. в статье Соглашение о вызовах x64.
Дополнительные сведения о соглашении __vectorcall
о вызовах см. в статье __vectorcall
.
Включение оптимизации компилятора x64
Следующий параметр компилятора позволяет оптимизировать приложение для архитектуры x64:
Тип x64 и макет хранилища
В этом разделе описывается хранилище типов данных для архитектуры x64.
Скалярные типы
Хотя можно получить доступ к данным с любым выравниванием, выровнять данные по своей естественной границе или несколько ее естественной границы, чтобы избежать потери производительности. Перечисления — это целочисленные константы, которые обрабатываются как 32-разрядные целые числа. В таблице ниже представлены определения типов и рекомендуемое место для хранения данных при использовании следующих значений выравнивания:
- байт — 8 битов;
- слово — 16 битов;
- двойное слово — 32 бита;
- учетверенное слово — 64 бита;
- увосьмеренное слово — 128 битов.
Скалярный тип | Тип данных C | Размер хранилища (в байтах) | Рекомендуемое выравнивание |
---|---|---|---|
INT8 |
char |
1 | Байт |
UINT8 |
unsigned char |
1 | Байт |
INT16 |
short |
2 | Word |
UINT16 |
unsigned short |
2 | Word |
INT32 |
int , long |
4 | Двойное слово |
UINT32 |
unsigned int , unsigned long |
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 и объединения
Другие типы, такие как массивы, структуры и объединения, предъявляют более строгие требования к выравниванию, обеспечивающие согласованное извлечение статических выражений и объединений. Ниже приведены определения для массивов, структур и объединений.
Массив
Содержит упорядоченную группу смежных объектов данных. Каждый объект называется элементом. Все элементы массива имеют одинаковый размер и тип данных.
Структура
Содержит упорядоченную группу объектов данных. В отличие от элементов массива, элементы структуры могут иметь различные типы и размеры данных.
Объединение
Объект, содержащий любой из наборов именованных элементов. Элементы именованного набора могут быть любого типа. Объем памяти, выделяемый для объединения, равен объему памяти, необходимому для хранения наибольшего его элемента, к которому прибавляется заполнение, требуемое для выравнивания.
В следующей таблице показано настоятельно рекомендуемое выравнивание скалярных членов профсоюзов и структур.
Скалярный тип | Тип данных в C | Требуемое выравнивание |
---|---|---|
INT8 |
char |
Байт |
UINT8 |
unsigned char |
Байт |
INT16 |
short |
Word |
UINT16 |
unsigned short |
Word |
INT32 |
int , long |
Двойное слово |
UINT32 |
unsigned int , unsigned long |
Двойное слово |
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
Типы данных, размер которых превышает 4 байта, не выравниваются автоматически в стеке при использовании компилятора x86 для компиляции приложения. Так как архитектура компилятора 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 флаг направления в регистре флагов ЦП должен сбрасываться.
Использование стека
Подробные сведения о выделении стека, выравнивании, типах функций и кадрах стека в архитектуре x64 см. в статье Использование стека для 64-разрядных систем.
Пролог и эпилог
Каждая функция, которая выделяет пространство стека, вызывает другие функции, сохраняет энергонезависимые регистры или использует обработку исключений, должна содержать пролог с ограничениями адресов, описанными в данных очистки, связанных с соответствующей записью таблицы функций, и эпилоги при каждом выходе из функции. Подробные сведения о необходимом коде пролога и эпилога в архитектуре x64 см. в статье Пролог и эпилог для 64-разрядных систем.
Обработка исключений в 64-разрядных системах
Сведения о соглашениях и структурах данных для реализации структурированной обработки исключений и обработки исключений C++ в архитектуре x64 см. в статье Обработка исключений в 64-разрядных системах.
Внутренний и подставляемый ассемблерный код
Одно из ограничений компилятора x64 не поддерживает встроенный сборщик. Это означает, что функции, которые не могут быть записаны в C или C++, должны быть записаны как вложенные или как встроенные функции, поддерживаемые компилятором. Некоторые функции чувствительны к производительности, а другие — нет. Функции, для которых производительность важна, должны реализовываться как встроенные.
Встроенные компоненты, поддерживаемые компилятором, описаны в встроенных функциях компилятора.
Формат изображения x64
Формат исполняемого образа x64 — PE32+. Размер исполняемых образов (как DLL, так и EXE) ограничен 2 гигабайтами, поэтому для адресации статических данных образов можно использовать относительную адресацию с 32-битным смещением. Эти данные включают в себя таблицу адресов импорта, строковые константы, статические глобальные данные и т. д.