среда, 29 сентября 2010 г.

Особенности выравнивания данных в структурах


Начинающим да и продолжающим программистам порой непонятно, почему в некоторых случаях размер структуры может быть больше, чем суммарный размер входящих в нее членов. Например:
struct Test
{
 char var1; // 1 байт
 short var2; // 2 байта
};
Как видно, суммарный размер структуры должен быть равен 3 байтам, на деле же оказывается вовсе не 3, а 4 байта.
Именно такой результат возвратит нам sizeof(Test). Все дело в выравнивание, которое осуществляет компилятор.
Т.е. данные помещаются по адресам, кратным размерам своих типов - 2-байтовые данные буду находятся по адресу, кратному 2, 4-байтные - по адресу, кратному 4 и т.д. (выравнивание по умолчанию)
Выравнивание нужно, потому что с выровненными данными процессор работает более эффективно. Если процессор обращается к невыровненным данным, то сначала он считывает одну часть данных, затем считывает другую часть, после этого компонует и получает исходные данные. Все это сокращает производительность.
Для выравнивания между данными помещаются специальные байты отступа (padding)

Рассмотрим примеры (ОС Windows).
struct Test
{
    unsigned char var1;
    unsigned char var2;
    unsigned char var3;
};
012345678
charcharchar...
В данном примере все просто. 3 однобайтовых данных выравниваются по однобайтовой границе.
В итоге суммарный размер равен 3 байта.

Другой пример:
struct Test
{
    unsigned char var1;
    unsigned short var2;
};
012345678
charPshort...
Здесь данные типа short будут выровнены по 2-байтовой границе, для этого будет использован отступ в 1 байт. Поменяем местами данные следующим образом:
struct Test
{
    unsigned short var1;
    unsigned char var2;
};
012345678
shortcharP...
В данном примере байт отступа будет использоваться для дополнения последнего члена структуры до размера, кратного максимальному размеру члена, т.е. до 2 байт. Таким образом, 1 байт на char + 1 байт на отступ = 2 байта (short)
Допустим у нас есть такая структура:
struct Test
{
    unsigned int var1;
    unsigned char var2;
};
012345678910
intcharPPP...
В данном случае в структуре присутствуют данные с максимальным размером 4 байта (int), следовательно последний член структуры необходимо дополнить до ближайшего кратного 4 числа, большего 1 (т.к. char), т.е. до 4 байтов. Для этого потребуется 3 байта для отступа.

Если имеется структура
struct Test
{
    unsigned int var1;
    unsigned short var2;
    unsigned char var3;
};

012345678910
int short char P ...
В данном случае член типа short не дополняется до размера int, потому что следом идет член типа char, который не требует выравнивания по 4-байтной границе. А после этого члена для выравнивания до 4 байт (число, кратное максимальному размеру типа в данной структуре (int) - 4 байта)

Аналогично рассмотрим структуру вида
struct Test
{
 double var1;
 unsigned int var2;
 unsigned short var3;
 unsigned char var4;
};
012345678910111213 14151617
double int short char P ...
Член типа double занимает как есть 8 байт, далее 4 байта на int, после которого байты отступа не требуются, т.к. граница выровнена уже по 4 байтам, а идущий следом член типа short требует всего лишь 2-байтового выравнивания. Затем один байт на char и байт отступа для выравнивания по числу, кратному размеру максимального типа (double), т.е. 16.

Представим такую ситуацию:
struct Test
{
    unsigned short var1;
    unsigned char var2;
    double var3;
};
012345678910 1112131415161718
short char PPPPP double ...
В данном примере данное типа char дополняется 1 байтом отступа до размера short, но далее идет член типа double, его рамер равен 8 байтами, поэтому он должен выравниваться по 8-байтовой границе. Следовательно отступ увеличивается еще на 4 недостающих байта.

ВНИМАНИЕ! Данные результаты получены при опции компилятора
Struct member alignment = Default;
Для значений /zp1, /zp2, zp[n] результаты будут отличны от полученных.

2 комментария :

Анонимный комментирует...

Руслан, а что будет с массивами внутри структур?

Руслан комментирует...

Масиивы - это подряд идущие элементы одного и того же типа. Например если рассматривать структуру вида
struct A
{
int iAr[2];
char chAr[2];
};
то ее можно свести к такому виду
struct A
{
int iVal1;
int iVal2;
char chVal1;
char chVal2;
};
Считаем: 4(int) + 4(int) + 1(char) + 1(char) + 2(padding до 4) = 12 байт