Почему оператор sizeof возвращает размер структуры больше, чем общие размеры членов структуры?
Анекдот: существовал настоящий компьютерный вирус, который поместил свой код в структурные дополнения в основной программе.
@Elazar Это впечатляет! Никогда бы не подумал, что можно использовать такие крошечные участки для чего-нибудь. Можете ли вы предоставить более подробную информацию?
@Wilson - Я уверен, что это было связано с большим количеством jmp.
См. Структуру набивка, упаковка: Утраченное искусство упаковки конструкции C Эрик С. Раймонд
Существует ли заполнение между двумя структурами, чтобы первый член следующей структуры начинался с выровненного адреса?
@Akshay Обе структуры начнут выравниваться (если вы не заставите их не быть ... побайтной сериализацией и т. д.)
У меня тоже есть эта проблема. Также у меня есть структура, в которой все битовые поля составляют до 128 бит. 128/8 = 16. Но когда я вызываю sizeof, я получаю 22 байта.
Любопытно, что спустя почти 12 лет после его выпуска и около 195 тысяч просмотров никто не заметил, что этот вопрос на самом деле совершенно неправильный.





Это связано с добавлением отступов для удовлетворения ограничений выравнивания. Выравнивание структуры данных влияет как на производительность, так и на правильность программ:
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 для изменения настроек выравнивания структуры.
Я хочу сделать здесь примечание: большинство процессоров наказывают вас за невыровненный доступ к памяти (как вы упомянули), но вы не можете забыть, что многие полностью его запрещают. В частности, большинство микросхем MIPS выдают исключение при невыровненном доступе.
Чипы x86 на самом деле довольно уникальны тем, что они допускают невыровненный доступ, хотя и наказываются; Чипы AFAIK самый будут вызывать исключения, а не только некоторые. PowerPC - еще один распространенный пример.
Включение прагм для невыровненного доступа обычно приводит к раздуванию размера вашего кода на процессорах, которые вызывают ошибки несовпадения, поскольку должен быть сгенерирован код для исправления каждого несовпадения. ARM также выдает ошибки несоосности.
@Dark - полностью согласен. Но настольные процессоры самый - x86 / x64, поэтому чипы самый не вызывают ошибок выравнивания данных;)
Невыровненный доступ к данным обычно является функцией архитектур CISC, и большинство архитектур RISC не включают ее (ARM, MIPS, PowerPC, Cell). Фактически, чипы самый НЕ являются настольными процессорами для встроенных систем, основанных на количестве чипов, и подавляющее большинство из них являются архитектурами RISC.
Ловушка невыровненного доступа используется (или, конечно, использовалась) в реализациях функционального языка для маркировки значений, чтобы их сборщики мусора могли знать, на какую произвольную память они смотрят. В общем, очень умный хак (согласно изречению Кернигана, слишком умный, чтобы использовать его в моем коде).
Чтобы быть педантичным, разве стандарт не гарантирует выравнивания, если все члены структуры имеют тип char?
@Kerrek SB: Стандарт гарантирует выравнивание для любой структуры независимо от используемых типов. Однако для char размером 1 байт невозможно выровнять его. Итак, стандарт гарантирует выравнивание, если все члены структуры являются символами БЕЗ КАКИХ-ЛИБО ЗАПОЛНЕНИЙ.
@LaraDougan: да, и каким-то образом есть простое правило, с помощью которого мы можем понять, почему это так. Стоимость за чип. Чипы x86 для настольных ПК - это потребительские товары за сотни долларов. Нет ничего терпимого для большинства промышленных применений, обычно промышленность имеет дело с чипами стоимостью менее 1 доллара или около того. Легко увидеть, насколько это повлияло на широкое распространение.
Почему для первого char есть 3 байта заполнения, а для следующих 2 только 1 байт?
@WayneO Количество отступов всегда достаточно, чтобы убедиться, что все, что будет дальше, выровнено в соответствии с его размером. Итак, в X есть 2 байта заполнения после short, чтобы гарантировать, что 4-байтовый int начинается на 4-байтовой границе. В Y после char есть заполнение одним байтом, чтобы убедиться, что 2 байта short начинается на 2-байтовой границе. Поскольку компилятор не может знать, что может быть после структуры в памяти (а это может быть много разных вещей), он готовится к худшему и вставляет достаточно заполнения, чтобы структура стала кратной 4 байтам. X требуется 3 байта для получения 12, Y требуется только 1 для 8.
«Чипы x86 имеют аппаратную поддержку невыровненного доступа» Верно. «Чипы x86 не вызывают ошибок выравнивания данных» Ложь. Это зависит от инструкции, инструкции SSE, в частности, имеют тенденцию к ошибкам из-за несовпадения (за исключением специальных невыровненных вариантов).
Существует ли заполнение между двумя структурами, чтобы первый член следующей структуры начинался с выровненного адреса?
@AkshayImmanuelD: Не "между" структурами, нет, это часть конца структуры. struct {long long a; char b;} обычно имеет 7 байтов заполнения в конце после b, что составляет всего 16 байтов. (на большинстве 64-битных архитектур yada yada)
Вопрос: если на одной машине в одной программе у меня есть две структуры S1 и S2 с одинаковыми членами. Требует ли стандарт, чтобы обе структуры имели одинаковый размер, или компилятор может решить использовать разные методы заполнения для S1 и S2?
@LaraDougan Я не думаю, что это было правдой, когда вы писали свой комментарий, и, вероятно, все еще ложно. Многие встроенные микросхемы не имеют концепции согласованного доступа к данным, потому что они обращаются только к 1 байту за один раз. Многие чипы по-прежнему 8-битные. (Возможно, это уже не большинство чипов, а 8-битные чипы, которых было большинство 12 лет назад).
Это возможно, если вы явно или неявно установили выравнивание структуры. Структура, выровненная по 4 байта, всегда будет кратна 4 байтам, даже если размер ее членов не кратен 4 байтам.
Также библиотека может быть скомпилирована под x86 с 32-битными целыми числами, и вы, возможно, сравниваете ее компоненты в 64-битном процессе, это дало бы вам другой результат, если бы вы делали это вручную.
Упаковка и выравнивание байтов, как описано в C FAQ здесь:
It's for alignment. Many processors can't access 2- and 4-byte quantities (e.g. ints and long ints) if they're crammed in every-which-way.
Suppose you have this structure:
struct { char a[3]; short int b; long int c; char d[3]; };Now, you might think that it ought to be possible to pack this structure into memory like this:
+-------+-------+-------+-------+ | a | b | +-------+-------+-------+-------+ | b | c | +-------+-------+-------+-------+ | c | d | +-------+-------+-------+-------+But it's much, much easier on the processor if the compiler arranges it like this:
+-------+-------+-------+ | a | +-------+-------+-------+ | b | +-------+-------+-------+-------+ | c | +-------+-------+-------+-------+ | d | +-------+-------+-------+In the packed version, notice how it's at least a little bit hard for you and me to see how the b and c fields wrap around? In a nutshell, it's hard for the processor, too. Therefore, most compilers will pad the structure (as if with extra, invisible fields) like this:
+-------+-------+-------+-------+ | a | pad1 | +-------+-------+-------+-------+ | b | pad2 | +-------+-------+-------+-------+ | c | +-------+-------+-------+-------+ | d | pad3 | +-------+-------+-------+-------+
Теперь в чем польза от слотов памяти pad1, pad2 и pad3.
@YoYoYonnY, это невозможно. Компилятор - не разрешено переупорядочивать элементы структуры, хотя gcc имеет экспериментальную возможность сделать это
@EmmEff, это может быть неправильно, но я не совсем понимаю: почему в массивах нет слота памяти для указателя?
@ BalázsBörcsök Это массивы постоянного размера, поэтому их элементы хранятся непосредственно в структуре с фиксированными смещениями. Компилятор знает все это во время компиляции, поэтому указатель является неявным. Например, если у вас есть структурная переменная этого типа с именем s, тогда &s.a == &s и &s.d == &s + 12 (с учетом выравнивания, показанного в ответе). Указатель сохраняется только в том случае, если массивы имеют переменный размер (например, a был объявлен как char a[] вместо char a[3]), но тогда элементы должны храниться в другом месте.
Это может быть связано с выравниванием байтов и заполнением, так что структура выходит на четное количество байтов (или слов) на вашей платформе. Например, в 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
«C в Linux с использованием gcc» недостаточно для описания вашей платформы. Выравнивание в основном зависит от архитектуры процессора.
- @ Кайл Бертон. Извините, я не понимаю, почему размер структуры someBits равен 4, я ожидаю 8 байтов, поскольку объявлено 2 целых числа (2 * sizeof (int)) = 8 байтов. Благодарность
Привет @ youpilat13, :2 и :6 на самом деле указывают 2 и 6 бит, а не полные 32-битные целые числа в этом случае. someBits.x, будучи всего 2 битами, может хранить только 4 возможных значения: 00, 01, 10 и 11 (1, 2, 3 и 4). Имеет ли это смысл? Вот статья об этой функции: geeksforgeeks.org/bit-fields-c
Если вы хотите, чтобы структура имела определенный размер с GCC, например, используйте __attribute__((packed)).
В Windows вы можете установить выравнивание на один байт при использовании компилятора cl.exe с Параметр / Zp.
Обычно процессору проще получить доступ к данным, кратным 4 (или 8), в зависимости от платформы, а также от компилятора.
Так что в основном это вопрос согласования.
У вас должны быть веские причины, чтобы это изменить.
"веские причины" Пример: обеспечение согласованности двоичной совместимости (заполнения) между 32-битными и 64-битными системами для сложной структуры в демонстрационном коде для проверки концепции, который будет продемонстрирован завтра. Иногда необходимость важнее приличия.
Все в порядке, кроме тех случаев, когда вы упоминаете операционную систему. Это проблема скорости ЦП, ОС вообще не задействована.
Еще одна веская причина - если вы вставляете поток данных в структуру, например при разборе сетевых протоколов.
@Blaisorblade Хотя архитектура ЦП является наиболее важным моментом, ОС также может иметь значение. Подумайте о процессоре x86, работающем в реальный режим (MS-DOS) по сравнению с защищенным режимом (Windows, Linux ...).
@dolmen Я только что указал, что фраза «Операционной системе легче получить доступ к данным» неверна, поскольку ОС не имеет доступа к данным.
@dolmen На самом деле следует говорить о ABI (бинарном интерфейсе приложения). Выравнивание по умолчанию (используется, если вы не меняете его в исходном коде) зависит от ABI, и многие ОС поддерживают несколько ABI (скажем, 32- и 64-разрядные, или для двоичных файлов из разных ОС, или для разных способов компиляции одинаковые двоичные файлы для той же ОС). OTOH, какое выравнивание удобно с точки зрения производительности, зависит от процессора - доступ к памяти осуществляется одинаково, независимо от того, используете ли вы 32- или 64-разрядный режим (я не могу комментировать реальный режим, но в настоящее время вряд ли актуален для производительности). IIRC Pentium начал отдавать предпочтение 8-байтовому выравниванию.
__attribute__((packed)) потенциально небезопасен в некоторых случаях: stackoverflow.com/q/8568432/827263
В компиляторах Microsoft вы должны использовать #pragma pack, делать это с параметром командной строки - зло. (GCC и clang в Windows используют __attribute__, как и в любой другой ОС)
В дополнение к другим ответам структура может (но обычно не иметь) виртуальных функций, и в этом случае размер структуры также будет включать пространство для vtbl.
Не совсем. В типичных реализациях к структуре добавляется vtable указатель.
Смотрите также:
для 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». Да, C99 говорит: «Внутри объекта структуры небитовые поля и единицы, в которых находятся битовые поля, имеют адреса, возрастающие в том порядке, в котором они объявлены». Больше стандартных качеств по адресу: stackoverflow.com/a/37032302/895245
Размер конструкции больше суммы ее частей из-за того, что называется упаковкой. Конкретный процессор имеет предпочтительный размер данных, с которыми он работает. Предпочтительный размер большинства современных процессоров - 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), в зависимости от упаковки компилятора по умолчанию.
Здесь обсуждаются последствия заполнения структуры, но это не дает ответа на вопрос.
«... из-за того, что называется упаковкой. ... - я думаю, вы имеете в виду« заполнение ».« Предпочтительный размер большинства современных процессоров, если 32 бита (4 байта) »- это немного упрощенное. Обычно поддерживаются размеры 8, 16, 32 и 64 бита; часто каждый размер имеет собственное выравнивание. И я ' Я не уверен, что ваш ответ добавляет новую информацию, которой еще нет в принятом ответе.
Когда я сказал об упаковке, я имел в виду, как компилятор упаковывает данные в структуру (и он может делать это, заполняя небольшие элементы, но это не нужно для заполнения, но он всегда упаковывает). Что касается размера - я говорил об архитектуре системы, а не о том, что система будет поддерживать для доступа к данным (что сильно отличается от базовой архитектуры шины). Что касается вашего последнего комментария, я дал упрощенное и расширенное объяснение одного аспекта компромисса (скорость по сравнению с размером) - основной проблемы программирования. Я также описываю способ решения проблемы - этого не было в принятом ответе.
«Упаковка» в этом контексте обычно относится к более жесткому распределению элементов, чем по умолчанию, как в случае с #pragma pack. Если элементы распределяются по их выравниванию по умолчанию, я бы обычно сказал, что структура упакована нет.
Упаковка - это своего рода перегруженный термин. Это означает, как вы помещаете элементы структуры в память. Аналогично смыслу складывания предметов в коробку (упаковка для перемещения). Это также означает помещение элементов в память без заполнения (что-то вроде сокращенного обозначения «плотно упаковано»). Затем есть командная версия слова в команде #pragma pack.
Язык C оставляет компилятору некоторую свободу в расположении структурных элементов в памяти:
Язык C дает некоторую уверенность программисту в расположении элементов в структуре:
Проблемы, связанные с выравниванием элементов:
Как работает выравнивание:
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: важно.
C99 N1256 стандартная тяга
http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf
6.5.3.4 Оператор sizeof:
3 When applied to an operand that has structure or union type, the result is the total number of bytes in such an object, including internal and trailing padding.
6.7.2.1 Спецификаторы структуры и объединения:
13 ... There may be unnamed padding within a structure object, but not at its beginning.
и:
15 There may be unnamed padding at the end of a structure or union.
Новый C99 гибкая функция элемента массива (struct S {int is[];};) также может влиять на заполнение:
16 As a special case, the last element of a structure with more than one named member may have an incomplete array type; this is called a flexible array member. In most situations, the flexible array member is ignored. In particular, the size of the structure is as if the flexible array member were omitted except that it may have more trailing padding than the omission would imply.
Приложение J Проблемы переносимости повторяет:
The following are unspecified: ...
- The value of padding bytes when storing values in structures or unions (6.2.6.1)
Стандартный проект C++ 11 N3337
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf
5.3.3 Размер:
2 When applied to a class, the result is the number of bytes in an object of that class including any padding required for placing objects of that type in an array.
9.2 Члены класса:
A pointer to a standard-layout struct object, suitably converted using a reinterpret_cast, points to its initial member (or if that member is a bit-field, then to the unit in which it resides) and vice versa. [ Note: There might therefore be unnamed padding within a standard-layout struct object, but not at its beginning, as necessary to achieve appropriate alignment. — end note ]
Я знаю C++ только для того, чтобы понять примечание :-)
Среди других хорошо объясненных ответов о выравнивании памяти и заполнении / упаковке структуры есть кое-что, что я обнаружил в самом вопросе, внимательно прочитав его.
"Why isn't
sizeoffor a struct equal to the sum ofsizeofof each member?""Why does the
sizeofoperator return a size larger for a structure than the total sizes of the structure's members"?
Оба вопроса предполагают нечто совершенно неправильное. По крайней мере, в общем, не ориентированном на примеры представлении, как здесь.
Результат операнда 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.
Это также объект второго по популярности вопроса о заполнении структур, здесь:
«Если нет причин для заполнения, память не будет заполнена». Это бесполезно и вводит в заблуждение. В языке есть определение, и оно не основано на нем. Он принадлежит к разделу типовых / гипотетических реализаций. (Который у вас есть). И тогда это тавтология. (Я понимаю, что это может быть риторическим).
См. Этот FAQ на C по выравниванию памяти. c-faq.com/struct/align.esr.html