Почему оператор sizeof
возвращает размер структуры больше, чем общие размеры членов структуры?
Почему sizeof для структуры не равен сумме sizeof каждого члена?
Ответы (12)
Это связано с добавлением отступов для удовлетворения ограничений выравнивания. Выравнивание структуры данных влияет как на производительность, так и на правильность программ:
- Неправильный доступ может быть серьезной ошибкой (часто
SIGBUS
). - Mis-aligned access might be a soft error.
- Either corrected in hardware, for a modest performance-degradation.
- Или исправлено эмуляцией в программном обеспечении для серьезного снижения производительности.
- Кроме того, атомарность и другие гарантии параллелизма могут быть нарушены, что приведет к незаметным ошибкам.
Вот пример использования типичных настроек для процессора 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
для изменения настроек выравнивания структуры.
char
есть 3 байта заполнения, а для следующих 2 только 1 байт?
- person WWZee; 26.01.2017
X
есть 2 байта заполнения после short
, чтобы гарантировать, что 4 байта int
начинаются на 4-байтовой границе. В Y
после char
добавляется 1 байт, чтобы убедиться, что 2 байта short
начинаются на 2-байтовой границе. Поскольку компилятор не может знать, что может быть после структуры в памяти (а это может быть много разных вещей), он готовится к худшему и вставляет достаточно заполнения, чтобы структура стала кратной 4 байтам. X
нужно 3 байта, чтобы получить 12, Y
только 1 для 8.
- person 8bittree; 17.02.2017
struct {long long a; char b;}
обычно имеет 7 байтов заполнения в конце после b
, что в сумме составляет 16 байтов. (на большинстве 64-битных архитектур yada yada)
- person Mooing Duck; 24.01.2019
Упаковка и выравнивание байтов, как описано в здесь часто задаваемых вопросов по 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 | +-------+-------+-------+-------+
s
, то &s.a == &s
и &s.d == &s + 12
(учитывая выравнивание, показанное в ответе). Указатель сохраняется только в том случае, если массивы имеют переменный размер (например, a
был объявлен char a[]
вместо char a[3]
), но тогда элементы должны храниться в другом месте.
- person kbolino; 31.03.2020
Если вы хотите, чтобы структура имела определенный размер с GCC, например, используйте _ 1_.
В Windows вы можете установить выравнивание в один байт при использовании компилятора cl.exe с / Zp option.
Обычно процессору проще получить доступ к данным, кратным 4 (или 8), в зависимости от платформы, а также от компилятора.
Так что в основном это вопрос согласования.
Для его изменения должны быть веские причины.
__attribute__((packed))
в некоторых случаях потенциально небезопасно: stackoverflow.com/q/8568432/827263
- person Keith Thompson; 10.06.2015
#pragma pack
, делать это с параметром командной строки - зло. (GCC и clang в Windows используют __attribute__
, как и в любой другой ОС)
- person Ben Voigt; 31.03.2019
#pragma pack(1)
- он поддерживается MSVC, gcc и clang, что делает ваш код более переносимым
- person mvp; 21.03.2021
Это может быть связано с выравниванием байтов и заполнением, так что структура выходит на четное количество байтов (или слов) на вашей платформе. Например, в 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
:2
и :6
на самом деле указывают 2 и 6 бит, а не полные 32-битные целые числа в этом случае. someBits.x, будучи всего 2 битами, может хранить только 4 возможных значения: 00, 01, 10 и 11 (1, 2, 3 и 4). Имеет ли это смысл? Вот статья об этой функции: geeksforgeeks.org/bit-fields-c
- person Kyle Burton; 13.07.2018
Смотрите также:
для Microsoft Visual C:
http://msdn.microsoft.com/en-us/library/2e70t5y1%28v=vs.80%29.aspx
и GCC заявляют о совместимости с компилятором Microsoft .:
https://gcc.gnu.org/onlinedocs/gcc-4.6.4/gcc/Structure_002dPacking-Pragmas.html
В дополнение к предыдущим ответам обратите внимание, что независимо от упаковки, в C ++ нет гарантии порядка участников. Компиляторы могут (и обязательно делают) добавлять в структуру указатель виртуальной таблицы и элементы базовых структур. Даже существование виртуальной таблицы не обеспечивается стандартом (реализация виртуального механизма не уточняется), поэтому можно сделать вывод, что такая гарантия просто невозможна.
Я совершенно уверен, что порядок членов гарантирован в C, но я бы не стал на это рассчитывать при написании кроссплатформенной или кросс-компиляторной программы.
Размер конструкции больше суммы ее частей из-за того, что называется упаковкой. Конкретный процессор имеет предпочтительный размер данных, с которыми он работает. Предпочтительный размер большинства современных процессоров - 32 бита (4 байта). Доступ к памяти, когда данные находятся на такой границе, более эффективен, чем доступ к памяти, находящейся на границе этого размера.
Например. Рассмотрим простую структуру:
struct myStruct
{
int a;
char b;
int c;
} data;
Если машина 32-битная и данные выровнены по 32-битной границе, мы видим немедленную проблему (при условии отсутствия выравнивания структуры). В этом примере предположим, что данные структуры начинаются с адреса 1024 (0x400 - обратите внимание, что два младших бита равны нулю, поэтому данные выровнены по 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%. Padding меняет местами эффективность памяти для повышения эффективности обработки. Учитывая, что компьютеры могут иметь огромный объем памяти (много гигабайт), компиляторы считают, что подкачка (скорость превышает размер) является разумной.
К сожалению, эта проблема становится смертельной, когда вы пытаетесь отправить структуры по сети или даже записать двоичные данные в двоичный файл. Заполнение, вставленное между элементами структуры или класса, может нарушить отправку данных в файл или сеть. Чтобы написать переносимый код (тот, который будет использоваться несколькими разными компиляторами), вам, вероятно, придется обращаться к каждому элементу структуры отдельно, чтобы гарантировать надлежащую «упаковку».
С другой стороны, разные компиляторы имеют разные возможности для управления упаковкой структур данных. Например, в 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), в зависимости от упаковки компилятора по умолчанию.
#pragma pack
. Если члены выделяются с выравниванием по умолчанию, я бы обычно сказал, что структура не упакована.
- person Keith Thompson; 13.06.2015
Это возможно, если вы явно или неявно установили выравнивание структуры. Структура, выровненная по 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 ... Внутри объекта структуры может быть безымянное заполнение, но не в его начале.
а также:
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. Размер только не включает размер гибкого массива, который доступен в C / C ++.
- Некоторые реализации языка позволяют управлять размещением структур в памяти с помощью прагмы и параметров компилятора.
Язык C дает некоторую уверенность программисту в расположении элементов в структуре:
- компиляторы, необходимые для назначения последовательности компонентов, увеличивающих адреса памяти
- Адрес первого компонента совпадает с начальным адресом структуры.
- безымянные битовые поля могут быть включены в структуру для требуемых адресных выравниваний соседних элементов
Проблемы, связанные с выравниванием элементов:
- Разные компьютеры по-разному выравнивают края объектов
- Различные ограничения на ширину битового поля
- Компьютеры различаются тем, как хранить байты одним словом (Intel 80x86 и Motorola 68000)
Как работает выравнивание:
- Объем, занимаемый конструкцией, рассчитывается как размер выровненного одиночного элемента массива таких структур. Конструкция должна заканчиваться так, чтобы первый элемент следующей следующей структуры не нарушал требований выравнивания.
p.s Более подробная информация доступна здесь: «Сэмюэл П. Харбисон, Гай Стил 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: выравнивание важно.
Среди других хорошо объясненных ответов о выравнивании памяти и заполнении / упаковке структуры есть кое-что, что я обнаружил в самом вопросе, внимательно прочитав его.
Почему
sizeof
для структуры не равно суммеsizeof
каждого члена?Почему оператор
sizeof
возвращает размер структуры больше, чем общие размеры ее членов?
Оба вопроса предполагают нечто совершенно неправильное. По крайней мере, в общем, не ориентированном на примеры представлении, как здесь.
Результат применения операнда sizeof
к объекту структуры может быть равен сумме sizeof
, примененной к каждому члену в отдельности. Он не должен быть больше / отличаться.
Если нет причин для заполнения, память не будет заполнена.
Одна из самых реализаций, если структура содержит только члены одного типа:
struct foo {
int a;
int b;
int c;
} bar;
Предполагая sizeof(int) == 4
, размер структуры bar
будет равен сумме размеров всех элементов вместе sizeof(bar) == 12
. Здесь нет отступов.
То же самое, например, здесь:
struct foo {
short int a;
short int b;
int c;
} bar;
Предполагая sizeof(short int) == 2
и sizeof(int) == 4
. Сумма выделенных байтов для a
и b
равна байтам, выделенным для c
, самого большого члена, и с этим все идеально выровнено. Таким образом, sizeof(bar) == 8
.
Это также объект второго по популярности вопроса о заполнении структур, здесь:
SO, answer
, _ 2_, другая ссылка - person EsmaeelE   schedule 09.12.2017