align (C++)

Блок, относящийся только к системам Microsoft

Используйте __declspec(align(#)) для точного управления выравниванием пользовательских данных (например, статическими распределениями или автоматическими данными в функции).

__declspec( align( # ) ) declarator

Заметки

Написание приложений, использующих последние инструкции процессора, связано с некоторыми новыми ограничениями и проблемами. В частности, для множества новых инструкций требуется, чтобы данные были выровнены по 16-байтовым границам. Кроме того, выравнивание часто используемых данных в соответствии с размером строки кэш-памяти конкретного процессора повышает производительность кэша. Например, при определении структуры размером менее 32 байт может потребоваться выровнять ее до 32 байт, чтобы добиться эффективного кэширования объектов этого типа структуры.

# — значение выравнивания. Допустимые записи — целые степени двух значений от 1 до 8192 (байты), например 2, 4, 8, 16, 32 или 64. declarator — это данные, которые объявляются как выровненные.

Сведения о возврате значения типа size_t, которое является требованием к выравниванию, см. в разделе __alignof. Раздел __unaligned содержит сведения об объявлении невыровненных указателей при разработке программ для 64-разрядных процессоров.

__declspec(align(#)) можно использовать при определении struct, union или class либо при объявлении переменной.

Компилятор не гарантирует и не пытается сохранить атрибут выравнивания данных при операции копирования или преобразования данных. Например, memcpy может копировать структуру, объявленную с помощью __declspec(align(#)), в любое место. Обратите внимание, что обычные распределители, например, malloc, оператор C++ new и распределители Win32, возвращают память, которая обычно недостаточно выровнена для структур __declspec(align(#)) и массивов структур. Чтобы гарантировать правильное выравнивание места назначения копирования или преобразования данных используйте _aligned_malloc или напишите собственный распределитель.

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

Без __declspec(align(#)) Visual C++ обычно выравнивает данные по естественным границам на основе процессора назначения и размера данных, вплоть до 4-байтовых границ на 32-разрядных процессорах и 8-байтовых границ на 64-разрядных процессорах. Данные в классах или структурах выравниваются в классе или в структуре по наименьшему естественному выравниванию с учетом текущего параметра упаковки (на основе параметра компилятора #pragma pack или /Zp).

В этом примере демонстрируется использование __declspec(align(#)):

__declspec(align(32)) struct Str1{
   int a, b, c, d, e;
};

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

Обратите внимание, что sizeof(struct Str1) равно 32. При этом подразумевается, что если создается массив объектов Str1, а базовый массив выровнен по 32 байтам, то каждый член массива также выровнен по 32 байтам. Для создания массива, база которого правильно выровнена в динамической памяти, используйте _aligned_malloc или напишите собственный распределитель.

Значение sizeof для любой структуры является суммой смещения последнего члена и его размера, округленной до ближайшего числа, кратного большему из следующих значений: значению выравнивания наибольшего члена или значению выравнивания всей структуры.

Компилятор использует эти правила для выравнивания структуры:

  • Если выравнивание не переопределяется с помощью __declspec(align(#)), выравнивание скалярного члена структуры — это его минимальный размер и текущая упаковка.

  • Если выравнивание не переопределяется с помощью __declspec(align(#)), выравнивание структуры — это максимальное число отдельных выравниваний членов.

  • Член структуры размещается со смещением от начала его родительской структуры. Смещение — это наименьшее число, кратное ее выравниванию, больше или равное смещению в конце предыдущего члена.

  • Размер структуры — это наименьшее число, кратное ее выравниванию, больше или равное смещению в конце его последнего члена.

__declspec(align(#)) может только увеличить ограничения выравнивания.

Дополнительные сведения см. в следующих разделах.

  • Примеры использования align

  • Определение новых типов с помощью __declspec(align(#))

  • Выравнивание данных в локальном хранилище потока

  • Использование align с упаковкой данных

  • Примеры выравнивания структуры (только для x64)

Примеры использования align

В следующих примерах показано, как __declspec(align(#)) влияет на размер и выравнивание структур данных. В примерах допускаются следующие определения.

#define CACHE_LINE  32
#define CACHE_ALIGN __declspec(align(CACHE_LINE))

В этом примере структура S1 определена с помощью __declspec(align(32)). Все случаи использования S1 для определения переменных или в объявлениях других типов выравниваются по 32 байтам. sizeof(struct S1) возвращает значение 32, а S1 имеет 16 байтов заполнения после 16 байтов, необходимых для удержания четырех целых чисел. Для каждого члена int требуется 4-байтовое выравнивание, но для структуры объявляется 32-байтовое выравнивание. Следовательно, общее выравнивание производится по 32 байтам.

struct CACHE_ALIGN S1 { // cache align all instances of S1
   int a, b, c, d;
};
struct S1 s1;   // s1 is 32-byte cache aligned

В этом примере sizeof(struct S2) возвращает 16. Это сумма размеров членов, поскольку это число является кратным наибольшему требуемому выравниванию (кратное 8).

__declspec(align(8)) struct S2 {
   int a, b, c, d;
};

В следующем примере sizeof(struct S3) возвращает 64.

struct S3 {
   struct S1 s1;   // S3 inherits cache alignment requirement
                  // from S1 declaration
   int a;         // a is now cache aligned because of s1
                  // 28 bytes of trailing padding
};

Обратите внимание, что в этом примере a выравнивается по своему естественному типу, то есть, в данном случае, по 4 байтам. Однако значение S1 должно быть выровнено по 32-байтовой границе. После a следует двадцать восемь байтов заполнения, чтобы значение s1 начиналось со смещением 32. Затем S4 наследует требование к выравниванию S1, поскольку это наибольшее требование к выравниванию в структуре. sizeof(struct S4) возвращает 64.

struct S4 {
   int a;
   // 28 bytes padding
    struct S1 s1;      // S4 inherits cache alignment requirement of S1
};

Следующие три объявления переменных также используют __declspec(align(#)). В каждом случае переменная должна быть выровнена по 32 байтам. В случае массива базовый адрес массива, а не каждый член массива, выравнивается по 32 байтам. Значение sizeof для каждого члена массива не изменяется при использовании __declspec(align(#)).

CACHE_ALIGN int i;
CACHE_ALIGN int array[128];
CACHE_ALIGN struct s2 s;

Для выравнивания каждого члена массива нужно использовать следующий код:

typedef CACHE_ALIGN struct { int a; } S5;
S5 array[10];

Обратите внимание, что в этом примере выравнивание самой структуры и первого элемента работают одинаково:

CACHE_ALIGN struct S6 {
   int a;
   int b;
};

struct S7 {
   CACHE_ALIGN int a;
               int b;
};

S6 и S7 имеют одинаковые характеристики выравнивания, выделения и размера.

В этом примере выравнивание начальных адресов a, b, c и d — соответственно 4, 1, 4, и 1.

void fn() { 
   int a;
   char b;
   long c;
   char d[10]
} 

Выравнивание при выделении памяти в куче зависит от того, какая функция выделения вызвана. Например, если используется malloc, результат зависит от размера операнда. Если arg >= 8, возвращаемая память выравнивается по 8 байтам. Если arg < 8, то память выравнивается по значению, которое является первой степенью 2, меньшей arg. Например, если использовать malloc(7), выравнивание выполняется по 4 байтам.

Определение новых типов с помощью __declspec(align(#))

Можно определить тип с характеристикой выравнивания.

Например, можно определить struct со значением выравнивания следующим образом:

struct aType {int a; int b;};
typedef __declspec(align(32)) struct aType bType;

Теперь aType и bType имеют одинаковый размер (8 байтов), но переменные типа bType будут выровнены по 32 байтам.

Выравнивание данных в локальном хранилище потока

Статическое локальное хранилище потока (TLS), созданное с помощью атрибута __declspec(thread) и помещенное в раздел TLS образа, обеспечивает выравнивание так же, как стандартные статические данные. Для создания данных TLS операционная система выделяет память в размере раздела TLS и сохраняет атрибут выравнивания раздела TLS.

В этом примере показаны различные способы помещения выровненных данных в локальное хранилище потока.

// put an aligned integer in TLS
__declspec(thread) __declspec(align(32)) int a;   

// define an aligned structure and put a variable of the struct type
// into TLS
__declspec(thread) __declspec(align(32)) struct F1 { int a; int b; } a;

// create an aligned structure 
struct CACHE_ALIGN S9 {
   int a;
   int b;
};
// put a variable of the structure type into TLS
__declspec(thread) struct S9 a;

Использование align с упаковкой данных

Параметр компилятора /Zp и директива pragma pack оказывают влияние на упаковку данных для структуры и членов объединения. В следующем примере показано совместное использование /Zp и __declspec(align(#)).

struct S {
   char a;
   short b;
   double c;
   CACHE_ALIGN double d;
   char e;
   double f;
};

В следующей таблице перечислено смещение каждого члена в различных значениях /Zp (или #pragma pack), показывая их взаимодействие.

Переменная

/Zp1

/Zp2

/Zp4

/Zp8

a

0

0

0

0

b

1

2

2

2

c

3

4

4

8

d

32

32

32

32

e

40

40

40

40

f

41

42

44

48

sizeof(S)

64

64

64

64

Для получения дополнительной информации см. /Zp (Выравнивание члена структуры).

Смещение объекта зависит от смещения предыдущего объекта и от текущего параметра упаковки, если у объекта нет атрибута __declspec(align(#)). Если же такой атрибут есть, то выравнивание зависит от смещения предыдущего объекта и от значения __declspec(align(#)) объекта.

См. также

Ссылки

__declspec

Основные понятия

Обзор соглашений ABI ARM

Общие сведения о соглашениях о вызовах для архитектуры x64