Выравнивание памяти в C-структурах

Я работаю на 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 ответов

Решение

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

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

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

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

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

#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

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

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

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

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

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

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

Предполагая, что:

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.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Другие вопросы по тегам