Выравнивание памяти в C-структурах - PullRequest
52 голосов
/ 25 марта 2011

Я работаю на 32-битной машине, поэтому я предполагаю, что выравнивание памяти должно быть 4 байта. Скажем, у меня есть структура:

typedef struct {
    unsigned short v1;
    unsigned short v2;
    unsigned short v3;
} myStruct;

реальный размер составляет 6 байтов, и я предполагаю, что выровненный размер должен быть 8, но sizeof(myStruct) возвращает мне 6.

Однако, если я напишу:

typedef struct {
    unsigned short v1;
    unsigned short v2;
    unsigned short v3;
    int i;
} myStruct;

реальный размер - 10 байтов, выровненный - 12, и на этот раз sizeof(myStruct) == 12.

Может кто-нибудь объяснить, в чем разница?

Ответы [ 10 ]

49 голосов
/ 25 марта 2011

По крайней мере, на большинстве машин тип всегда выровнен только по границе, равной самому типу [Править: вы не можете требовать выравнивания "больше", чем это, потому что вы должны иметь возможность создавать массивы , и вы не можете вставить отступ в массив]. В вашей реализации short, по-видимому, составляет 2 байта, а int 4 байта.

Это означает, что ваша первая структура выровнена по 2-байтовой границе. Поскольку все члены имеют размер 2 байта, между ними не вставляется отступ.

Второй содержит 4-байтовый элемент, который выравнивается по 4-байтовой границе. Так как ему предшествуют 6 байтов, 2 байта заполнения вставляются между v3 и i, давая 6 байтов данных в short с, два байта заполнения и еще 4 байта данных в int в общей сложности 12.

19 голосов
/ 25 марта 2011

Забудьте о наличии разных членов, даже если вы напишите две структуры, члены которых точно одинаковы, с разница в том, что порядок, в котором они объявлены, отличается, тогда Размер каждой структуры может быть (и часто бывает) разным.

Например, посмотрите это,

#include <iostream>
using namespace std;
struct A
{
   char c;
   char d;
   int i; 
};
struct B
{
   char c;
   int i;   //note the order is different!
   char d;
};
int main() {
        cout << sizeof(A) << endl;
        cout << sizeof(B) << endl;
}

Скомпилируйте его с gcc-4.3.4, и вы получите такой вывод:

8
12

То есть размеры разные, хотя в обеих структурах одни и те же члены!

Код в Ideone: http://ideone.com/HGGVl

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

13 голосов
/ 25 марта 2011

По умолчанию значения выровнены в соответствии с их размером.Таким образом, 2-байтовое значение, например short, выровнено по 2-байтовой границе, а 4-байтовое значение, например int, выровнено по 4-байтовой границе

. В вашем примере 2байты заполнения добавляются перед i, чтобы гарантировать, что i попадает на 4-байтовую границу.

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

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

Компиляторы обычно позволяют вам контролировать упаковку с помощью (например) #pragma pack директив.

5 голосов
/ 25 марта 2011

Предполагая:

sizeof(unsigned short) == 2
sizeof(int)            == 4

Тогда я лично использовал бы следующее (ваш компилятор может отличаться):

unsigned shorts are aligned to 2 byte boundaries
int will be aligned to 4 byte boundaries.


typedef struct
{
   unsigned short v1;    // 0 bytes offset
   unsigned short v2;    // 2 bytes offset
   unsigned short v3;    // 4 bytes offset
} myStruct;              // End 6 bytes.


// No part is required to align tighter than 2 bytes. 
// So whole structure can be 2 byte aligned.

typedef struct
{
    unsigned short v1;      // 0 bytes offset
    unsigned short v2;      // 2 bytes offset
    unsigned short v3;      // 4 bytes offset
    /// Padding             // 6-7 padding (so i is 4 byte aligned
    int i;                  // 8 bytes offset
} myStruct;                 // End 12 bytes

// Whole structure needs to be 4 byte aligned.
// So that i is correctly aligned.
4 голосов
/ 25 марта 2011

Во-первых, несмотря на то, что особенности заполнения оставлены на усмотрение компилятора, ОС также накладывает некоторые правила относительно требований выравнивания.В этом ответе предполагается, что вы используете gcc, хотя ОС может различаться

Чтобы определить пространство, занимаемое данной структурой и ее элементами, вы можете следовать следующим правилам:

Сначала предположим, чтоСтруктура всегда начинается с адреса, который правильно выровнен для всех типов данных.

Затем для каждой записи в структуре:

  • Минимальное необходимое пространство - этонеобработанный размер элемента, заданный sizeof(element).
  • Требование выравнивания элемента является требованием выравнивания базового типа элемента.Примечательно, что это означает, что требование выравнивания для массива char[20] такое же, как требование для простого char.

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

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

Теперь сами требования к выравниванию также немного странны.

  • 32-битный Linux требует, чтобы 2-байтовые типы данных имели 2-байтовое выравнивание (их адреса должны быть четными).Все большие типы данных должны иметь 4-байтовое выравнивание (адреса, оканчивающиеся на 0x0, 0x4, 0x8 или 0xC).Обратите внимание, что это относится и к типам, размер которых превышает 4 байта (например, double и long double).
  • 32-битная Windows более строгая в том смысле, что если тип имеет размер K байтов, он долженбыть выровненным по байту.Это означает, что double можно разместить только по адресу, оканчивающемуся на 0x0 или 0x8.Единственное исключение из этого - long double, который по-прежнему выровнен по 4 байта, хотя на самом деле он имеет длину 12 байтов.
  • Для Linux и Windows на 64-битных машинах тип K байтов долженбыть выровненным по байту.Опять же, long double является исключением и должно быть выровнено в 16 байт.
2 голосов
/ 25 марта 2011

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

Во второй структуре int (предположительно 32 бита) должен быть выровнен по словам, чтобы он вставлял отступ между v3 и i для выравнивания i.

2 голосов
/ 25 марта 2011

Каждый тип данных должен быть выровнен по границе памяти своего собственного размера. Таким образом, short должен быть выровнен на 2-байтовой границе, а int должен быть на 4-байтовой границе. Точно так же long long должен быть на 8-байтовой границе.

1 голос
/ 25 марта 2011

Причина, по которой второй sizeof(myStruct) равен 12, - это заполнение, которое вставляется между v3 и i для выравнивания i на 32-битной границе. Это два байта.

Википедия достаточно четко объясняет отступы и выравнивание.

0 голосов
/ 25 марта 2011

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

0 голосов
/ 25 марта 2011

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

...