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(#)) объекта.