Точное понимание

Одной из низкоуровневых особенностей C++ является возможность указать точное выравнивание объектов в памяти, чтобы максимально использовать конкретную аппаратную архитектуру. По умолчанию компилятор выравнивает члены класса и структуры по их значению размера: bool и на 1-байтовых границах, short на 2-байтовых границах, intlongа также float на 4-байтовых границах, а doublelong longтакже long double на 8-байтовых границахchar.

В большинстве сценариев вам никогда не нужно беспокоиться о выравнивании, так как выравнивание по умолчанию уже оптимально. Однако в некоторых случаях можно добиться значительных улучшений производительности или экономии памяти, указав настраиваемое выравнивание для структур данных. Перед Visual Studio 2015 можно использовать ключевое слово майкрософт __alignof и __declspec(align) указать выравнивание больше по умолчанию. Начиная с Visual Studio 2015, следует использовать стандартные ключевое слово alignof C++11 и alignas максимальное переносимость кода. Новые ключевое слово ведут себя так же, как и расширения, относящиеся к Корпорации Майкрософт. Документация по этим расширениям также применяется к новым ключевое слово. Дополнительные сведения см. в разделе alignof "Оператор", " alignas Описатель" и "Выравнивание". Стандарт C++ не указывает поведение упаковки для выравнивания границ меньше границ, чем компилятор по умолчанию для целевой платформы, поэтому в этом случае необходимо использовать корпорацию Майкрософт #pragma pack .

Используйте класс aligned_storage для выделения памяти структур данных с настраиваемыми выравниваниями. Класс aligned_union предназначен для указания выравнивания для профсоюзов с нетривиальными конструкторами или деструкторами.

Выравнивание и адреса памяти

Выравнивание представляет собой свойство адреса памяти, выражаемое как числовой адрес по модулю степени 2. Например, адрес 0x0001103F модулем 4 равен 3. Этот адрес, как говорят, выравнивается на 4n+3, где 4 указывает выбранную мощность 2. Выравнивание адреса зависит от выбранной мощности 2. Тот же адрес по модулю 8 равен 7. Говорят, что адрес выровнен по X, если его выравнивание — Xn+0.

ЦП выполняют инструкции, работающие с данными, хранящимися в памяти. Данные определяются их адресами в памяти. Один datum также имеет размер. Мы называем datum естественным образом выровненным , если его адрес выровнен по размеру. В противном случае это называется неправильно. Например, 8-байтовый байтовый датум с плавающей запятой естественно выравнивается, если адрес, используемый для идентификации, имеет выравнивание 8-байтов.

Обработка выравнивания данных компилятором

Компиляторы пытаются сделать выделение данных таким образом, чтобы предотвратить несоответствие данных.

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

Компилятор также помещает структуры таким образом, чтобы естественно выравнивать каждый элемент структуры. Рассмотрим структуру struct x_ в следующем примере кода:

struct x_
{
   char a;     // 1 byte
   int b;      // 4 bytes
   short c;    // 2 bytes
   char d;     // 1 byte
} bar[3];

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

В следующем примере кода показано, как компилятор помещает заполненную структуру в память:

// Shows the actual memory layout
struct x_
{
   char a;            // 1 byte
   char _pad0[3];     // padding to put 'b' on 4-byte boundary
   int b;            // 4 bytes
   short c;          // 2 bytes
   char d;           // 1 byte
   char _pad1[1];    // padding to make sizeof(x_) multiple of 4
} bar[3];

Оба объявления возвращаются sizeof(struct x_) как 12 байтов.

Второе объявление включает два дополняющих элемента:

  1. char _pad0[3] для выравнивания int b элемента на границе 4-байтов.
  2. char _pad1[1] для выравнивания элементов массива структуры struct _x bar[3]; на границе 4-байтов.

Заполнение выравнивает элементы таким образом, чтобы обеспечить естественный bar[3] доступ.

В следующем примере кода показан макет массива bar[3] :

adr offset   element
------   -------
0x0000   char a;         // bar[0]
0x0001   char pad0[3];
0x0004   int b;
0x0008   short c;
0x000a   char d;
0x000b   char _pad1[1];

0x000c   char a;         // bar[1]
0x000d   char _pad0[3];
0x0010   int b;
0x0014   short c;
0x0016   char d;
0x0017   char _pad1[1];

0x0018   char a;         // bar[2]
0x0019   char _pad0[3];
0x001c   int b;
0x0020   short c;
0x0022   char d;
0x0023   char _pad1[1];

alignof и alignas.

Описатель alignas — это переносимый стандартный способ указания пользовательского выравнивания переменных и определяемых пользователем типов. Оператор alignof также является стандартным переносимым способом получения выравнивания указанного типа или переменной.

Пример

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

// alignas_alignof.cpp
// compile with: cl /EHsc alignas_alignof.cpp
#include <iostream>

struct alignas(16) Bar
{
    int i;       // 4 bytes
    int n;      // 4 bytes
    alignas(4) char arr[3];
    short s;          // 2 bytes
};

int main()
{
    std::cout << alignof(Bar) << std::endl; // output: 16
}

См. также

Выравнивание структуры данных