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

StackOverflow https://stackoverflow.com/questions/119123

Вопрос

Почему этот 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 */

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

ВАЖНОЕ ЗАМЕЧАНИЕ:Как в стандартах C, так и в C ++ указано, что выравнивание структуры определяется реализацией.Поэтому каждый компилятор может выбрать выравнивание данных по-разному, что приведет к различным и несовместимым макетам данных.По этой причине при работе с библиотеками, которые будут использоваться разными компиляторами, важно понимать, как компиляторы выравнивают данные.Некоторые компиляторы имеют настройки командной строки и / или специальные #pragma инструкции для изменения параметров выравнивания структуры.

Другие советы

Упаковка и выравнивание байтов, как описано в разделе часто задаваемых вопросов по C здесь:

Это для выравнивания.Многие процессоры не могут получить доступ к 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 байта данных.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.Без pragma у меня могло быть что угодно от 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 Оператор sizeof:

3 При применении к операнду, который имеет структуру или тип объединения, результатом является общее количество байтов в таком объекте, включая внутреннее и завершающее заполнение.

6.7.2.1 Спецификаторы структуры и объединения:

13 ...Внутри объекта structure может быть неназванное заполнение , но не в его начале.

и:

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 ++ только для того, чтобы понять примечание :-)

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

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

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

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

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

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

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

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

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

p.s Более подробная информация доступна здесь:"Сэмюэл П.Харбисон, Гай Л.Стил В качестве ссылки, (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: выравнивание очень важно.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top