Рубрики

Выравнивание элементов структуры, заполнение и упаковка данных

Что мы подразумеваем под выравниванием данных, упаковкой структуры и заполнением?

Прогнозировать вывод следующей программы.

#include <stdio.h>

  
// Требования выравнивания
// (типичная 32-битная машина)

  
// символ 1 байт
// короткий int 2 байта
// int 4 байта
// удваиваем 8 байтов

  
// структура А

typedef struct structa_tag

{

   char        c;

   short int   s;

} structa_t;

  
// структура B

typedef struct structb_tag

{

   short int   s;

   char        c;

   int         i;

} structb_t;

  
// структура C

typedef struct structc_tag

{

   char        c;

   double      d;

   int         s;

} structc_t;

  
// структура D

typedef struct structd_tag

{

   double      d;

   int         s;

   char        c;

} structd_t;

  

int main()

{

   printf("sizeof(structa_t) = %d\n", sizeof(structa_t));

   printf("sizeof(structb_t) = %d\n", sizeof(structb_t));

   printf("sizeof(structc_t) = %d\n", sizeof(structc_t));

   printf("sizeof(structd_t) = %d\n", sizeof(structd_t));

  

   return 0;

}

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

Каждый тип данных в C / C ++ будет иметь требование выравнивания (в действительности это предписывается архитектурой процессора, а не языком). Процессор будет иметь длину слова обработки, равную длине шины данных. На 32-битной машине размер слова обработки будет 4 байта.

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

Адресация памяти все еще будет последовательной. Если банк 0 занимает адрес X, банк 1, банк 2 и банк 3 будут иметь адреса (X + 1), (X + 2) и (X + 3). Если целое число 4 байта выделено на X-адресе (X кратно 4), процессору требуется только один цикл памяти, чтобы прочитать все целое число.

Если как, если целое число выделено по адресу, отличному от кратного 4, оно охватывает два ряда банков, как показано на рисунке ниже. Такое целое число требует двух циклов чтения из памяти для извлечения данных.

Выравнивание данных переменной связано с тем, как данные хранятся в этих банках. Например, естественное выравнивание int на 32-битной машине составляет 4 байта. Когда тип данных выровнен естественным образом, процессор выбирает его в минимальных циклах чтения.

Точно так же естественное выравнивание короткого int составляет 2 байта. Это означает, что короткий int может храниться в паре банк 0 — банк 1 или пара банк 2 — банк 3. Дабл требует 8 байтов и занимает две строки в банках памяти. Любое смещение double заставит более двух циклов считывания извлекать данные double .

Обратите внимание, что двойная переменная будет размещена на 8-байтовой границе на 32-битной машине и требует двух циклов чтения из памяти. На 64-битной машине, основанной на количестве банков, двойная переменная будет размещена на границе 8 байт и требует только один цикл чтения из памяти.

Структура Padding:

В C / C ++ структуры используются в качестве пакета данных. Он не предоставляет никаких функций инкапсуляции или сокрытия данных (случай C ++ является исключением из-за его семантического сходства с классами).

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

Вывод вышеуказанной программы:

Для удобства предположим, что каждая переменная типа структуры размещена на 4-байтовой границе (скажем, 0x0000), то есть базовый адрес структуры кратен 4 (не обязательно всегда, см. Объяснение structc_t).

структура А

Первый элемент structa_t — это char, который выровнен на один байт, за которым следует short int . short int выровнен на 2 байта. Если короткий элемент int сразу выделяется после элемента char, он начинается с нечетной границы адреса. Компилятор вставит дополнительный байт после символа, чтобы у короткого int был адрес, кратный 2 (т. Е. Выровненный по 2 байта). Общий размер structa_t будет: sizeof (char) + 1 (заполнение) + sizeof (короткий), 1 + 1 + 2 = 4 байта.

структура Б

Первый член structb_t — это короткий int, за которым следует char. Поскольку char может находиться на любой границе байта, между короткими int и char не требуется заполнение, в общей сложности они занимают 3 байта. Следующий член — int. Если int выделяется сразу, он начинается с нечетной границы байта. Нам нужно 1 байтовое заполнение после символа char, чтобы адрес следующего элемента int был выровнен на 4 байта. В целом, structb_t требует 2 + 1 + 1 (заполнение) + 4 = 8 байтов.

структура C — каждая структура также будет иметь требования к выравниванию

Применяя тот же анализ, structc_t требуется sizeof (char) + заполнение 7 байтов + sizeof (double) + sizeof (int) = 1 + 7 + 8 + 4 = 20 байтов. Однако sizeof (structc_t) будет 24 байта. Это потому, что, наряду с членами структуры, переменные типа структуры также будут иметь естественное выравнивание. Позвольте нам понять это на примере. Скажем, мы объявили массив structc_t, как показано ниже

structc_t structc_array[3];

Предположим, базовый адрес structc_array для простоты вычислений равен 0x0000. Если structc_t занимает 20 (0x14) байтов, как мы рассчитывали, второй элемент массива structc_t (индексированный как 1) будет иметь значение 0x0000 + 0x0014 = 0x0014. Это начальный адрес элемента индекса 1 массива. Двойной член этого structc_t будет размещен в 0x0014 + 0x1 + 0x7 = 0x001C (десятичное 28), которое не кратно 8 и противоречит требованиям выравнивания double. Как мы упоминали сверху, требование выравнивания double составляет 8 байтов.

Чтобы избежать такого смещения, компилятор введет требование выравнивания для каждой структуры. Это будет как у самого крупного члена структуры. В нашем случае выравнивание structa_t равно 2, structb_t равно 4 и structc_t равно 8. Если нам нужны вложенные структуры, размер наибольшей внутренней структуры будет выравниванием ближайшей большей структуры.

В structc_t вышеприведенной программы после элемента int будет заполнение 4 байтами, чтобы размер структуры был кратен ее выравниванию. Таким образом, sizeof (structc_t) составляет 24 байта. Это гарантирует правильное выравнивание даже в массивах. Вы можете проверить.

структура D — Как уменьшить отступы?

К настоящему времени может быть ясно, что заполнение неизбежно. Есть способ минимизировать отступы. Программист должен объявить элементы структуры в порядке увеличения / уменьшения размера. Примером является structd_t, приведенный в нашем коде, размер которого составляет 16 байтов вместо 24 байтов structc_t.

Что такое структура упаковки?

Иногда необходимо избегать заполненных байтов среди членов структуры. Например, чтение содержимого заголовка файла ELF или заголовка файла BMP или JPEG. Нам нужно определить структуру, аналогичную структуре макета заголовка, и отобразить ее. Однако следует проявлять осторожность при доступе к таким членам. Обычно чтение побайтовое является вариантом, чтобы избежать смещения исключений. Там будет удар по производительности.

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

Ошибка указателя:

Существует вероятность потенциальной ошибки при работе с арифметикой указателя. Например, разыменование общего указателя (void *), как показано ниже, может привести к смещению исключения,

// Deferencing a generic pointer (not safe)
// There is no guarantee that pGeneric is integer aligned
*(int *)pGeneric;

Это возможно над типом кода в программировании. Если указатель pGeneric не выровнен в соответствии с требованиями к типу приведенных данных, существует возможность получить смещенное исключение.

Infact немногие процессоры не будут иметь последние два бита декодирования адреса, и нет никакого способа получить доступ к неправильному адресу. Процессор генерирует смещенное исключение, если программист пытается получить доступ к такому адресу.

Замечание по malloc () вернул указатель

Указатель, возвращаемый функцией malloc (), void * . Он может быть преобразован в любой тип данных в соответствии с потребностями программиста. Реализатор malloc () должен возвращать указатель, выровненный по максимальному размеру примитивных типов данных (определенных компилятором). Обычно он выровнен по 8-байтовой границе на 32-битных машинах.

Выравнивание файла объекта, Выравнивание раздела, Выравнивание страницы

Они специфичны для разработчиков операционных систем, разработчиков компиляторов и выходят за рамки данной статьи. На самом деле, у меня мало информации.

Общие вопросы:

1. Применяется ли выравнивание для стека?

Да. Стек также является памятью. Системный программист должен загрузить указатель стека с адресом памяти, который правильно выровнен. Как правило, процессор не проверяет выравнивание стека, ответственность за правильное выравнивание стековой памяти лежит на программисте. Любое смещение вызовет сюрпризы во время выполнения.

Например, если длина слова процессора составляет 32 бита, указатель стека также должен быть выровнен, чтобы быть кратным 4 байтам.

2. Если данные чарса размещены в банке другого банка 0, они будут помещены в неправильные строки данных во время чтения из памяти. Как процессор обрабатывает тип char ?

Обычно процессор распознает тип данных на основе инструкций (например, LDRB на процессоре ARM). В зависимости от банка, в котором он хранится, процессор сдвигает байт на младшие строки данных.

3. Когда аргументы передаются в стек, подвергаются ли они выравниванию?

Да. Компилятор помогает программисту в правильном выравнивании. Например, если 16-битное значение помещается в стек шириной 32 бита, значение автоматически дополняется нулями до 32 бит. Рассмотрим следующую программу.

void argument_alignment_check( char c1, char c2 )

{

   // Учитывая нисходящий стек

   // (на восходящем стеке вывод будет отрицательным)

   printf("Displacement %d\n", (int)&c2 - (int)&c1);

}

Выход будет 4 на 32-битной машине. Это связано с тем, что каждый символ занимает 4 байта из-за требований выравнивания.

4. Что произойдет, если мы попытаемся получить доступ к смещенным данным?

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

5. Есть ли способ запросить требования выравнивания типа данных.

Да. Компиляторы предоставляют нестандартные расширения для таких нужд. Например, __alignof () в Visual Studio помогает получить требования к выравниванию типа данных. Читайте MSDN для деталей.

6. Когда чтение памяти эффективно при чтении 4 байтов за раз на 32-битной машине, почему двойной тип должен быть выровнен по границе 8 байтов?

Важно отметить, что большинство процессоров будет иметь математический сопроцессор, называемый блоком с плавающей запятой (FPU). Любая операция с плавающей точкой в коде будет преобразована в инструкции FPU. Основной процессор не имеет ничего общего с выполнением с плавающей запятой. Все это будет сделано за кулисами.

По стандарту двойной тип будет занимать 8 байтов. И каждая операция с плавающей запятой, выполняемая в FPU, будет иметь длину 64 бита. Даже типы с плавающей запятой будут переведены в 64-битную версию перед выполнением.

64-битная длина регистров FPU заставляет двойной тип выделяться на 8-байтовой границе. Я предполагаю (у меня нет конкретной информации) в случае операций FPU, выборка данных может быть другой, я имею в виду шину данных, так как она идет в FPU. Следовательно, декодирование адреса будет отличаться для двойных типов (что ожидается на границе 8 байтов). Это означает, что схемы декодирования адреса блока с плавающей запятой не будут иметь последние 3 контакта .

ответы:

sizeof(structa_t) = 4
sizeof(structb_t) = 8
sizeof(structc_t) = 24
sizeof(structd_t) = 16

Обновление: 1 мая 2013

Замечено, что на последних процессорах мы получаем размер struct_c как 16 байтов. Мне еще предстоит ознакомиться с соответствующей документацией. Я буду обновлять, как только получу правильную информацию (написано для нескольких специалистов по аппаратному обеспечению).

На старых процессорах (AMD Athlon X2), использующих тот же набор инструментов (GCC 4.7), я получил размер struct_c размером 24 байта. Размер зависит от того, как организована банковская память на аппаратном уровне.

— — — Венки . Пожалуйста, пишите комментарии, если вы обнаружите что-то неправильное или вы хотите поделиться дополнительной информацией по обсуждаемой выше теме.

Рекомендуемые посты:

Выравнивание элементов структуры, заполнение и упаковка данных

0.00 (0%) 0 votes