Выравнивание памяти в 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 байтов данных в short
s, два байта заполнения и еще 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. Конечно, все зависит от используемого компилятора и от того, какие настройки он использует.