Почему размер sizeof для структуры не равен сумме sizeof каждого члена?

Почему оператор sizeof возвращает размер, более крупный для структуры, чем общие размеры членов структуры?

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

  • Недопустимый доступ может быть жесткой ошибкой (часто SIGBUS ).
  • Неравномерный доступ может быть мягкой ошибкой.
    • Либо исправлено в аппаратном обеспечении, либо для снижения производительности.
    • Или исправлено путем эмуляции в программном обеспечении, для серьезной деgradleации производительности.
    • Кроме того, атомарность и другие гарантии параллелизма могут быть нарушены, что приводит к незначительным ошибкам.

Вот пример использования типичных настроек для процессора 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 инструкции для изменения настроек выравнивания структуры.

Выравнивание упаковки и байтов, как описано в FAQ по C здесь :

Это для выравнивания. Многие процессоры не могут получить доступ к 2- и 4-байтовым количествам (например, ints и long ints), если они переполнены каждым способом.

Предположим, что у вас есть эта структура:

 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 байта данных.c находятся в 0x405, 0x406, 0x407, 0x408. На 32-битной машине система считывала data.c в течение одного цикла памяти, но получала бы только 3 из 4 байтов (четвертый байт находится на следующей границе). Таким образом, системе потребуется второй доступ к памяти для получения 4-го байта,

Теперь, если вместо того, чтобы поместить data.c по адресу 0x405, компилятор заполнил структуру на 3 байта и поместил data.c по адресу 0x408, тогда системе понадобится всего 1 цикл для чтения данных, сокращая время доступа к этому элементу данных на 50%. Заполняет эффективность памяти для эффективности обработки. Учитывая, что компьютеры могут иметь огромные объемы памяти (много гигабайт), компиляторы считают, что обмен (скорость по размеру) является разумным.

К сожалению, эта проблема становится убийцей при попытке отправить структуры по сети или даже записать двоичные данные в двоичный файл. Прокладка, вставленная между элементами структуры или classа, может нарушить данные, отправленные в файл или сеть. Чтобы написать переносимый код (тот, который будет использоваться для нескольких разных компиляторов), вам, вероятно, придется обращаться к каждому элементу структуры отдельно, чтобы обеспечить надлежащую «упаковку».

С другой стороны, разные компиляторы имеют разные возможности для управления упаковкой структуры данных. Например, в 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-битными int и вы можете сравнить ее компоненты с 64-битным процессом, это даст вам другой результат, если вы делаете это вручную.

Стандартная тяга C99 N1256

http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf

6.5.3.4 Оператор sizeof :

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 При применении к classу результатом является количество байтов в объекте этого classа, включая любое дополнение, необходимое для размещения объектов этого типа в массиве.

9.2 Члены classа :

Указатель на объект структуры стандартного макета, соответствующим образом преобразованный с использованием reinterpret_cast, указывает на его начальный член (или если этот элемент является битовым полем, а затем блоку, в котором он находится) и наоборот. [Примечание. Таким образом, в рамках объекта структуры стандартного макета может быть указано неназванное заполнение, но не в его начале, по мере необходимости, для достижения соответствующего выравнивания. – конечная нота]

Я знаю достаточно C ++, чтобы понять примечание 🙂

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

Язык C оставляет компилятору некоторую свободу в отношении расположения структурных элементов в памяти:

  • могут появляться отверстия памяти между любыми двумя компонентами и после последнего компонента. Это было связано с тем, что определенные типы объектов на конечном компьютере могут быть ограничены границами адресации
  • размер “ячеек памяти”, включенный в результат оператора sizeof. Размер sizeof не включает в себя размер гибкого массива, который доступен в C / C ++
  • Некоторые реализации языка позволяют управлять расположением памяти структур через параметры прагмы и компилятора

Язык C предоставляет некоторую уверенность программисту в компоновке элементов в структуре:

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

Проблемы, связанные с выравниванием элементов:

  • Различные компьютеры выравнивают края объектов по-разному
  • Различные ограничения на ширину битового поля
  • Компьютеры отличаются тем, как хранить байты в слове (Intel 80×86 и Motorola 68000)

Как работает выравнивание:

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

ps Более подробная информация доступна здесь: «Сэмюэл П. Харбисон, Гай Л.Стеле CA Reference, (5.6.2 – 5.6.7)”

Идея заключается в том, что для соображений скорости и кеширования операнды должны считываться с адресов, соответствующих их естественному размеру. Чтобы это произошло, компиляторы создают элементы структуры, чтобы следующий член или следующая структура были выровнены.

 struct pixel { unsigned char red; // 0 unsigned char green; // 1 unsigned int alpha; // 4 (gotta skip to an aligned offset) unsigned char blue; // 8 (then skip 9 10 11) }; // next offset: 12 

Архитектура x86 всегда могла получать неверные адреса. Тем не менее, он медленнее, и когда несоосность перекрывает две разные линии кэша, тогда она вытесняет две строки кэша, когда выровненный доступ будет вытеснять только один.

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

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

TL; DR: выравнивание важно.

  • Как определить структуру typedef, содержащую указатели на себя?
  • структурированная сериализация в C и передача через MPI
  • Структурный конструктор в C ++?
  • Нечитаемая только альтернатива анонимным типам
  • Каковы различия между структурой и classом в C ++?
  • Структура структуры памяти C
  • Как включить динамический массив INSIDE a struct в C?
  • Копирование структуры, содержащей указатели на устройство CUDA
  • C: sizeof single struct member
  • Маршал C ++ struct array в C #
  • Обоснование макроса container_of в linux / list.h
  • Давайте будем гением компьютера.