Почему sizeof структуры не равен сумме sizeof её членов?
Почему оператор sizeof
возвращает размер структуры, который больше суммы размеров её членов?
5 ответ(ов)
Это может быть связано с выравниванием байтов и дополнениями, чтобы структура занимала четное количество байтов (или слов) на вашей платформе. Например, в C на Linux следующие три структуры:
#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:
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 бит, то мы можем столкнуться с проблемой (предполагая отсутствие выравнивания структуры). Допустим, что структура data
начинается по адресу 1024 (0x400 - обратите внимание, что младшие 2 бита нулевые, следовательно, данные выровнены на 32-битной границе). Доступ к data.a
будет работать нормально, так как он начинается на границе - 0x400. Доступ к data.b
также будет корректным, так как он находится по адресу 0x404 - снова на 32-битной границе. Однако, если структура не выровнена, то data.c
окажется по адресу 0x405. Четыре байта data.c
будут находиться по адресам 0x405, 0x406, 0x407 и 0x408. На 32-битной машине система прочитает data.c
за один цикл памяти, но получит только 3 из 4 байтов (четвертый байт находится на следующей границе). Таким образом, системе придется выполнить второй доступ к памяти, чтобы получить четвертый байт.
Если бы вместо помещения data.c
по адресу 0x405 компилятор добавил бы 3 байта заполнителя и разместил data.c
по адресу 0x408, то системе потребовался бы только 1 цикл для чтения данных, что сократило бы время доступа к этому элементу данных на 50%. Заполнение меняет эффективность памяти на эффективность обработки. Учитывая, что компьютеры могут иметь огромные объемы памяти (многие гигабайты), компиляторы считают, что такая замена (скорость на размер) является разумной.
К сожалению, эта проблема становится критичной, когда вы пытаетесь передать структуры по сети или записать бинарные данные в бинарный файл. Заливка, вставленная между элементами структуры или класса, может нарушить данные, отправляемые в файл или across сеть. Чтобы написать переносимый код (который будет совместим с несколькими разными компиляторами), вам, вероятно, придется обращаться к каждому элементу структуры отдельно, чтобы обеспечить правильное «упаковку».
С другой стороны, разные компиляторы имеют разные возможности для управления упаковкой структур данных. Например, в Visual C/C++ компилятор поддерживает команду #pragma pack
. Это позволит вам настроить упаковку и выравнивание данных.
Например:
#pragma pack(1)
struct MyStruct
{
int a;
char b;
int c;
short d;
} myData;
I = sizeof(myData);
Теперь размер myData
должен составлять 11. Без #pragma
этот размер может варьироваться от 11 до 14 (и для некоторых систем даже до 32), в зависимости от настроек упаковки по умолчанию у компилятора.
Да, это возможно, если вы явно или неявно задали выравнивание структуры. Структура, выровненная на 4 байта, всегда будет кратна 4 байтам, даже если размеры ее членов не кратны 4 байтам.
Также стоит отметить, что библиотека может быть скомпилирована под x86 с использованием 32-битных целых чисел, а вы можете сравнивать ее компоненты в 64-битном процессе, что даст вам другой результат, если бы вы делали это вручную.
В языке C компилятору предоставляется некоторая свобода в расположении структурных элементов в памяти:
- В памяти могут возникать "пустоты" между любыми двумя компонентами и после последней компоненты. Это связано с тем, что определенные типы объектов на целевом компьютере могут быть ограничены границами адресации.
- Размер "пустот" включается в результат оператора sizeof. Однако оператор sizeof не учитывает размер гибкого массива, который доступен в C/C++.
- Некоторые реализации языка позволяют контролировать расположение памяти структур с помощью pragma и опций компилятора.
Язык C также предоставляет некоторую гарантию программисту относительно расположения элементов в структуре:
- Компиляторы обязаны назначать последовательность компонентов с увеличением адресов в памяти.
- Адрес первого компонента совпадает с начальным адресом структуры.
- Безымянные битовые поля могут быть включены в структуру для обеспечения необходимого выравнивания адресов смежных элементов.
Существует ряд проблем, связанных с выравниванием элементов:
- Разные компьютеры выравнивают границы объектов по-разному.
- Существуют различные ограничения на ширину битового поля.
- Компьютеры могут по-разному хранить байты в слове (например, Intel 80x86 и Motorola 68000).
Как работает выравнивание:
- Объем, занимаемый структурой, рассчитывается как размер выровненного единственного элемента массива таких структур. Структура должна завершаться так, чтобы первый элемент следующей структуры не нарушал требования по выравниванию.
p.s. Более подробную информацию можно найти в "Samuel P. Harbison, Guy L. Steele C A Reference, (5.6.2 - 5.6.7)".
Разница между const int*, const int * const и int * const?
Почему в макросах используются, казалось бы, бессмысленные операторы do-while и if-else?
Что такое Правило трёх?
Имеют ли круглые скобки после имени типа значение при использовании new?
`unsigned int` против `size_t`: когда и что использовать?