Поделиться через


Общие сведения о соглашениях 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 Слово
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 Octaword

Макет агрегатов и объединений x64

Другие типы, такие как массивы, структуры и союзы, предъявляют более строгие требования к выравниванию, что обеспечивает согласованное хранение и извлечение агрегатов и союзов. Ниже приведены определения для массивов, структур и объединений.

  • Массив

    Содержит упорядоченную группу смежных объектов данных. Каждый объект называется элементом. Все элементы массива имеют одинаковый размер и тип данных.

  • Структура

    Содержит упорядоченную группу объектов данных. В отличие от элементов массива, элементы структуры могут иметь различные типы и размеры данных.

  • Объединение

    Объект, содержащий любой из наборов именованных элементов. Элементы именованного набора могут быть любого типа. Размер памяти, выделяемой для объединения, равен размеру памяти, необходимому для хранения его крупнейшего элемента, плюс добавочные байты, требуемые для выравнивания.

В следующей таблице показано строго рекомендуемое выравнивание скалярных членов объединений и структур.

Скалярный тип Тип данных в C Требуемое выравнивание
INT8 char Байт
UINT8 unsigned char Байт
INT16 short Слово
UINT16 unsigned short Слово
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
}

Схема, показывающая расположение структуры для примера 1. На схеме показаны 2 байта памяти. Элемент a, типа short, занимает байты с 0 по 1.

Пример 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
}

Схема, показывающая макет структуры, например 2.

На схеме показаны 24 байта памяти. Член a, int, занимает байты от 0 до 3. На схеме показана заполнение байтов 4–7. Член b, двойной, занимает байты 8–15. Член c, короткий, занимает байты 16–17. Байты 18–23 не используются.

Пример 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
}

Схема, показывающая макет структуры, например 3.

На схеме показаны 12 байт памяти. Член a, char, занимает байт 0. Байт 1 является заполнением. Член b, короткий, занимает байты 2–4. Член c, char, занимает байт 4. Байты 5–7 являются заполняющими. Член d, int, занимает байты 8–11.

Пример 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
}

Схема, показывающая расположение объединения для примера 4.

На схеме показаны 8 байт памяти. Член p, char, занимает байт 0. Свойство s, типа short, занимает байты с 0 по 1. Элемент l, длинное, занимает байты с 0 по 3. Байты 4–7 заполнены.

Битовые поля

Битовые поля структуры ограничены 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 см. в разделе об использовании стека x64.

Пролог и эпилог

Каждая функция, которая выделяет пространство стека, вызывает другие функции, сохраняет невлетающие регистры или использует обработку исключений, должна содержать пролог с ограничениями адресов, описанными в данных распаковки стека, связанных с соответствующей записью таблицы функций, и эпилоги на каждом выходе из функции. Подробные сведения о необходимом коде пролога и эпилога в архитектуре x64 см. в статье Пролог и эпилог для 64-разрядных систем.

Обработка исключений в 64-разрядных системах

Сведения о соглашениях и структурах данных для реализации структурированной обработки исключений и обработки исключений C++ в архитектуре x64 см. в статье Обработка исключений в 64-разрядных системах.

Встроенные функции и встраиваемый ассемблерный код

Одно из ограничений компилятора x64 заключается в том, что он не поддерживает встроенный ассемблер. Это означает, что функции, которые не могут быть записаны в C или C++, должны быть записаны как вложенные или как встроенные функции, поддерживаемые компилятором. Некоторые функции чувствительны к производительности, а другие — нет. Функции, для которых производительность важна, должны реализовываться как встроенные.

Встроенные компоненты, поддерживаемые компилятором, описаны в встроенных функциях компилятора.

Формат изображения x64

Формат исполняемого образа x64 — PE32+. Размер исполняемых образов (как DLL, так и EXE) ограничен 2 гигабайтами, поэтому для адресации статических данных образов можно использовать относительную адресацию с 32-битным смещением. Эти данные включают в себя таблицу адресов импорта, строковые константы, статические глобальные данные и т. д.

См. также

Соглашения о вызовах