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

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

586

Решение

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

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

569

Другие решения

Упаковка и выравнивание байтов, как описано в 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  |
+-------+-------+-------+-------+
154

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

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

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

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

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

23

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

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

для 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, но я бы не стал рассчитывать на это при написании кроссплатформенной или кросс-компиляторной программы.

9

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

6

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

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

5

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 ++, чтобы понять примечание 🙂

5
По вопросам рекламы ammmcru@yandex.ru
Adblock
detector