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



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

1513   11  

11 ответов:

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

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

это для выравнивания. Многие процессоры не могут получить доступ к 2 - и 4-байтовые количествах (например, int или длинный ИНЦ) если они забиты в разные стороны.

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

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 обернуться вокруг? В двух словах, это трудно для процессора, тоже. Таким образом, большинство компиляторов будет pad структура (как будто с дополнительными, невидимыми полями) такая:

+-------+-------+-------+-------+
|           a           | pad1  |
+-------+-------+-------+-------+
|       b       |     pad2      |
+-------+-------+-------+-------+
|               c               |
+-------+-------+-------+-------+
|           d           | pad3  |
+-------+-------+-------+-------+

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

в Windows вы можете установить выравнивание в один байт при использовании cl.exe compier с опция/ЗП.

обычно ЦП легче получить доступ к данным, кратным 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++нет гарантии членов-заказа. Компиляторы могут (и, конечно же, делают) добавлять в структуру указатель виртуальной таблицы и элементы базовых структур. Даже наличие виртуальной таблицы не обеспечивается стандартом (реализация виртуального механизма не указана) и поэтому можно сделать вывод, что такая гарантия просто невозможна.

Я уверен member-order и гарантировано в C, но я бы не рассчитывал на это, при написании кросс-платформенных и кросс-компилятор программа.

размер структуры больше, чем сумма ее частей из-за того, что называется упаковкой. Конкретный процессор имеет предпочтительный размер данных, с которым он работает. Предпочтительный размер большинства современных процессоров - 32 бита (4 байта). Доступ к памяти, когда данные находятся на такой границе, более эффективен, чем вещи, которые охватывают эту границу размера.

например. Рассмотрим простую структуру:

struct myStruct
{
   int a;
   char b;
   int c;
} data;

Если машина 32-разрядная машина и данные выровненный на 32-битной границе, мы видим непосредственную проблему (предполагая отсутствие выравнивания структуры). В этом примере Предположим, что данные структуры начинаются с адреса 1024 (0x400 - обратите внимание, что самые низкие 2 бита равны нулю, поэтому данные выровнены по 32-разрядной границе). Доступ к данным.a будет работать нормально, потому что он начинается на границе - 0x400. Доступ к данным.b также будет работать нормально, потому что он находится по адресу 0x404 - еще одна 32-разрядная граница. Но невыровненная структура поместила бы данные.c at адрес 0x405. 4 байта данных.c находятся в 0x405, 0x406, 0x407, 0x408. На 32-разрядной машине система считывала бы данные.c в течение одного цикла памяти, но получит только 3 из 4 байтов (4-й байт находится на следующей границе). Таким образом, система должна была бы сделать второй доступ к памяти, чтобы получить 4-й байт,

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

С99 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 Sizeof:

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

9.2 членов класса:

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

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

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

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

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

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

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

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

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

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

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

p.s более подробная информация доступна здесь: "Samuel P. Harbison, Guy L. Steele C a 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: выравнивание имеет важное значение.

Comments

    Ничего не найдено.