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


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

Схема, на которой показан пример макета структуры 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.

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

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

Разряды

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

См. также

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