Почему sizeof
оператор возвращает размер, больший для структуры, чем общий размер элементов структуры?
Это связано с добавлением отступов для удовлетворения ограничений выравнивания. Выравнивание структуры данных влияет как на производительность, так и на правильность программ:
SIGBUS
).Вот пример использования типовых настроек для процессора x86 (все использовали 32- и 64-битные режимы):
struct X
{
short s; /* 2 bytes */
/* 2 padding bytes */
int i; /* 4 bytes */
char c; /* 1 byte */
/* 3 padding bytes */
};
struct Y
{
int i; /* 4 bytes */
char c; /* 1 byte */
/* 1 padding byte */
short s; /* 2 bytes */
};
struct Z
{
int i; /* 4 bytes */
short s; /* 2 bytes */
char c; /* 1 byte */
/* 1 padding byte */
};
const int sizeX = sizeof(struct X); /* = 12 */
const int sizeY = sizeof(struct Y); /* = 8 */
const int sizeZ = sizeof(struct Z); /* = 8 */
Можно минимизировать размер структур путем сортировки элементов по выравниванию (для базовых типов сортировки по размеру достаточно) (например, структура) Z
в приведенном выше примере).
ВАЖНОЕ ПРИМЕЧАНИЕ. В стандартах C и C ++ говорится, что выравнивание структуры определяется реализацией. Поэтому каждый компилятор может по-разному выравнивать данные, что приводит к разным и несовместимым макетам данных. По этой причине при работе с библиотеками, которые будут использоваться разными компиляторами, важно понимать, как компиляторы выравнивают данные. Некоторые компиляторы имеют настройки командной строки и / или специальные #pragma
заявления для изменения настроек выравнивания структуры.
Упаковка и выравнивание байтов, как описано в C FAQ Вот:
Это для выравнивания. Многие процессоры не могут получить доступ к 2- и 4-байтовым
количества (например, целые и длинные целые), если они забиты
каждый, который-путь.Предположим, у вас есть эта структура:
struct { char a[3]; short int b; long int c; char d[3]; };
Теперь вы можете подумать, что это можно упаковать
структура в памяти, как это:+-------+-------+-------+-------+ | a | b | +-------+-------+-------+-------+ | b | c | +-------+-------+-------+-------+ | c | d | +-------+-------+-------+-------+
Но это намного, намного проще на процессоре, если компилятор организует
это так:+-------+-------+-------+ | a | +-------+-------+-------+ | b | +-------+-------+-------+-------+ | c | +-------+-------+-------+-------+ | d | +-------+-------+-------+
В упакованной версии обратите внимание, что это по крайней мере немного сложно для
ты и я, чтобы увидеть, как оборачиваются поля b и c? В двух словах,
это тоже сложно для процессора. Поэтому большинство компиляторов будут дополняться
структура (как будто с дополнительными, невидимыми полями), как это:+-------+-------+-------+-------+ | a | pad1 | +-------+-------+-------+-------+ | b | pad2 | +-------+-------+-------+-------+ | c | +-------+-------+-------+-------+ | d | pad3 | +-------+-------+-------+-------+
Если вы хотите, чтобы структура имела определенный размер с GCC, например, используйте __attribute__((packed))
.
В Windows вы можете установить выравнивание на один байт при использовании компилятора cl.exe с / Zp опция.
Обычно процессору проще получить доступ к данным, кратным 4 (или 8), в зависимости от платформы, а также от компилятора.
Так что это вопрос выравнивания в принципе.
У вас должны быть веские причины, чтобы изменить это.
Это может быть связано с выравниванием байтов и заполнением, так что структура выходит на четное число байтов (или слов) на вашей платформе. Например в C на Linux, следующие 3 структуры:
#include "stdio.h"
struct oneInt {
int x;
};
struct twoInts {
int x;
int y;
};
struct someBits {
int x:2;
int y:6;
};int main (int argc, char** argv) {
printf("oneInt=%zu\n",sizeof(struct oneInt));
printf("twoInts=%zu\n",sizeof(struct twoInts));
printf("someBits=%zu\n",sizeof(struct someBits));
return 0;
}
У членов, чьи размеры (в байтах) составляют 4 байта (32 бита), 8 байтов (2x 32 бита) и 1 байт (2 + 6 бит) соответственно. Вышеприведенная программа (в Linux с использованием gcc) печатает размеры как 4, 8 и 4, где последняя структура дополняется так, чтобы это было одно слово (4 x 8 битных байтов на моей 32-битной платформе).
oneInt=4
twoInts=8
someBits=4
Смотрите также:
для Microsoft Visual C:
http://msdn.microsoft.com/en-us/library/2e70t5y1%28v=vs.80%29.aspx
и GCC заявляют о совместимости с компилятором Microsoft.
http://gcc.gnu.org/onlinedocs/gcc/Structure_002dPacking-Pragmas.html
В дополнение к предыдущим ответам, обратите внимание, что независимо от упаковки, в C ++ нет гарантии порядка членов. Компиляторы могут (и, безусловно, делают) добавлять в структуру указатель виртуальной таблицы и члены базовых структур. Даже существование виртуальной таблицы не обеспечивается стандартом (реализация виртуального механизма не указана), и поэтому можно сделать вывод, что такая гарантия просто невозможна.
Я совершенно уверен членом порядка является гарантировано в C, но я бы не стал рассчитывать на это при написании кроссплатформенной или кросс-компиляторной программы.
Размер структуры больше, чем сумма ее частей из-за того, что называется упаковкой. Определенный процессор имеет предпочтительный размер данных, с которым он работает. Предпочитаемый размер большинства современных процессоров — 32 бита (4 байта). Доступ к памяти, когда данные находятся на границе такого типа, более эффективен, чем те, которые охватывают границу этого размера.
Например. Рассмотрим простую структуру:
struct myStruct
{
int a;
char b;
int c;
} data;
Если машина является 32-разрядной, и данные выровнены по 32-разрядной границе, мы видим непосредственную проблему (при условии отсутствия выравнивания структуры). В этом примере предположим, что данные структуры начинаются с адреса 1024 (0x400 — обратите внимание, что младшие 2 бита равны нулю, поэтому данные выровнены по 32-битной границе). Доступ к data.a будет работать нормально, потому что он начинается на границе — 0x400. Доступ к data.b также будет работать нормально, потому что он находится по адресу 0x404 — еще одна 32-битная граница. Но не выровненная структура поместит data.c по адресу 0x405. 4 байта data.c находятся в 0x405, 0x406, 0x407, 0x408. На 32-битной машине система считывает data.c в течение одного цикла памяти, но получает только 3 из 4 байтов (4-й байт находится на следующей границе). Таким образом, система должна сделать второй доступ к памяти, чтобы получить 4-й байт,
Теперь, если вместо того, чтобы поместить data.c по адресу 0x405, компилятор дополнил структуру на 3 байта и поместил data.c по адресу 0x408, тогда системе понадобился бы только 1 цикл для чтения данных, что сократило бы время доступа к этому элементу данных на 50%. Заполнение заменяет эффективность памяти на эффективность обработки. Учитывая, что компьютеры могут иметь огромное количество памяти (много гигабайт), компиляторы считают, что обмен (скорость на размер) является разумным.
К сожалению, эта проблема становится опасной, когда вы пытаетесь отправить структуры по сети или даже записать двоичные данные в двоичный файл. Заполнение, вставленное между элементами структуры или класса, может нарушить данные, отправляемые в файл или сеть. Для того чтобы написать переносимый код (тот, который будет идти к нескольким различным компиляторам), вам, вероятно, придется обращаться к каждому элементу структуры отдельно, чтобы обеспечить надлежащую «упаковку».
С другой стороны, разные компиляторы имеют разные возможности для управления упаковкой структуры данных. Например, в Visual C / C ++ компилятор поддерживает команду #pragma pack. Это позволит вам настроить упаковку и выравнивание данных.
Например:
#pragma pack 1
struct MyStruct
{
int a;
char b;
int c;
short d;
} myData;
I = sizeof(myData);
Теперь у меня должна быть длина 11. Без прагмы я мог бы быть любым от 11 до 14 (а для некоторых систем — до 32), в зависимости от упаковки компилятора по умолчанию.
Это можно сделать, если вы явно или неявно настроили выравнивание структуры. Структура с выравниванием 4 всегда будет кратна 4 байтам, даже если размер ее членов будет не кратным 4 байтам.
Кроме того, библиотека может быть скомпилирована под x86 с 32-разрядными целочисленными значениями, и вы, возможно, сравниваете ее компоненты в 64-разрядном процессе, что даст вам другой результат, если вы будете делать это вручную.
C99 N1256 стандартная тяга
http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf
6.5.3.4 Размер оператора:
3 При применении к операнду, который имеет структуру или тип объединения,
результат — общее количество байтов в таком объекте,
в том числе внутренняя и задняя обивка.
6.7.2.1 Структура и объединение спецификаторов:
13 … Там может быть безымянный
заполнение внутри объекта структуры, но не в его начале.
а также:
15 Там может быть безымянный отступ в конце структуры или объединения.
Новый C99 функция гибкого массива (struct S {int is[];};
) также может повлиять на отступы:
16 В особом случае последний элемент структуры с более чем одним именованным элементом может
иметь неполный тип массива; это называется членом гибкого массива. В большинстве ситуаций
член гибкого массива игнорируется. В частности, размер структуры, как если бы
гибкий элемент массива был опущен, за исключением того, что он может иметь больше конечного заполнения, чем
упущение будет означать.
Приложение J Проблемы переносимости подтверждает:
Следующее не указано: …
- Значение байтов заполнения при хранении значений в структурах или объединениях (6.2.6.1)
C ++ 11 N3337 стандартная версия
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf
5.3.3 Размер:
2 При применении
для класса, результатом является количество байтов в объекте этого класса, включая любые дополнения, необходимые для
размещение объектов этого типа в массиве.
9.2 Члены класса:
Указатель на объект структуры стандартной компоновки, соответствующим образом преобразованный с помощью reinterpret_cast, указывает на его
начальный элемент (или, если этот элемент является битовым полем, то к модулю, в котором он находится), и наоборот. [ Заметка:
Следовательно, в объекте структуры стандартной компоновки может быть безымянный отступ, но не в его начале,
по мере необходимости для достижения соответствующего выравнивания. — конец примечания]
Я только знаю достаточно C ++, чтобы понять примечание 🙂