Почему sizeof для структуры не равен сумме sizeof каждого члена? - PullRequest
615 голосов
/ 23 сентября 2008

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

Ответы [ 11 ]

595 голосов
/ 23 сентября 2008

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

  • Несоответствующий доступ может быть серьезной ошибкой (часто 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 для изменения настроек выравнивания структуры.

161 голосов
/ 23 сентября 2008

Упаковка и выравнивание байтов, как описано в 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  |
+-------+-------+-------+-------+
23 голосов
/ 23 сентября 2008

Если вы хотите, чтобы структура имела определенный размер с GCC, например, используйте __attribute__((packed)).

В Windows вы можете установить выравнивание на один байт при использовании компилятора cl.exe с параметром / Zp .

Обычно процессору проще получить доступ к данным, кратным 4 (или 8), в зависимости от платформы, а также от компилятора.

Так что это в основном вопрос выравнивания.

У вас должны быть веские причины для его изменения.

13 голосов
/ 23 сентября 2008

Это может быть связано с выравниванием байтов и заполнением, так что структура выходит на четное число байтов (или слов) на вашей платформе. Например, в 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
9 голосов
/ 31 мая 2011

Смотри также:

для Microsoft Visual C:

http://msdn.microsoft.com/en-us/library/2e70t5y1%28v=vs.80%29.aspx

и GCC заявляют о совместимости с компилятором Microsoft ::1008*

http://gcc.gnu.org/onlinedocs/gcc/Structure_002dPacking-Pragmas.html

В дополнение к предыдущим ответам, пожалуйста, обратите внимание, что независимо от упаковки, в C ++ нет гарантии заказа членов. Компиляторы могут (и, безусловно, делают) добавлять в структуру указатель виртуальной таблицы и члены базовых структур. Даже существование виртуальной таблицы не обеспечивается стандартом (реализация виртуального механизма не указана), и поэтому можно сделать вывод, что такая гарантия просто невозможна.

Я вполне уверен, член-порядок гарантированно гарантирован в C , но я бы не стал рассчитывать на это при написании кроссплатформенной или кросс-компиляционной программы.

6 голосов
/ 10 июня 2015

Размер структуры больше, чем сумма ее частей из-за того, что называется упаковкой. Определенный процессор имеет предпочтительный размер данных, с которым он работает. Предпочтительный размер большинства современных процессоров - 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), в зависимости от упаковки компилятора по умолчанию.

5 голосов

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

http://www.open -std.org / ОТК1 / 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 / ОТК1 / SC22 / wg21 / документы / документы / 2012 / n3337.pdf

5.3.3 Размер :

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

9.2 Члены класса :

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

Я знаю только достаточно C ++, чтобы понять примечание: -)

5 голосов
/ 23 сентября 2008

Это может быть сделано, если вы явно или неявно настроили выравнивание структуры. Структура с выравниванием 4 всегда будет кратна 4 байтам, даже если размер ее членов будет не кратным 4 байтам.

Также библиотека может быть скомпилирована в x86 с 32-битными целыми числами, и вы, возможно, сравниваете ее компоненты в 64-битном процессе, если бы вы делали это вручную, вы получили бы другой результат.

4 голосов
/ 23 сентября 2008

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

3 голосов
/ 29 июля 2015

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

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

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

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

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

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

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

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

p.s Более подробная информация доступна здесь: "Samuel P.Harbison, Guy L.Steele C A Reference, (5.6.2 - 5.6.7)"

...