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

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

См. Этот FAQ на C по выравниванию памяти. c-faq.com/struct/align.esr.html

Richard Chambers 26.04.2013 16:40

Анекдот: существовал настоящий компьютерный вирус, который поместил свой код в структурные дополнения в основной программе.

Elazar 02.09.2013 21:58

@Elazar Это впечатляет! Никогда бы не подумал, что можно использовать такие крошечные участки для чего-нибудь. Можете ли вы предоставить более подробную информацию?

OmarL 14.11.2016 21:59

@Wilson - Я уверен, что это было связано с большим количеством jmp.

hoodaticus 28.06.2017 00:59

См. Структуру набивка, упаковка: Утраченное искусство упаковки конструкции C Эрик С. Раймонд

EsmaeelE 09.12.2017 16:53
SO, answer, Geeks4Geeks, другая ссылка
EsmaeelE 09.12.2017 16:55

Существует ли заполнение между двумя структурами, чтобы первый член следующей структуры начинался с выровненного адреса?

AlphaGoku 07.02.2018 07:18

@Akshay Обе структуры начнут выравниваться (если вы не заставите их не быть ... побайтной сериализацией и т. д.)

Matthew M. 14.04.2018 18:26

У меня тоже есть эта проблема. Также у меня есть структура, в которой все битовые поля составляют до 128 бит. 128/8 = 16. Но когда я вызываю sizeof, я получаю 22 байта.

user12211554 09.12.2019 22:48

Любопытно, что спустя почти 12 лет после его выпуска и около 195 тысяч просмотров никто не заметил, что этот вопрос на самом деле совершенно неправильный.

RobertS supports Monica Cellio 29.06.2020 17:27
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
737
10
214 812
12
Перейти к ответу Данный вопрос помечен как решенный

Ответы 12

Ответ принят как подходящий

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

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

Serafina Brocious 23.09.2008 08:27

Чипы x86 на самом деле довольно уникальны тем, что они допускают невыровненный доступ, хотя и наказываются; Чипы AFAIK самый будут вызывать исключения, а не только некоторые. PowerPC - еще один распространенный пример.

Dark Shikari 23.09.2008 11:08

Включение прагм для невыровненного доступа обычно приводит к раздуванию размера вашего кода на процессорах, которые вызывают ошибки несовпадения, поскольку должен быть сгенерирован код для исправления каждого несовпадения. ARM также выдает ошибки несоосности.

Mike Dimmick 23.09.2008 15:16

@Dark - полностью согласен. Но настольные процессоры самый - x86 / x64, поэтому чипы самый не вызывают ошибок выравнивания данных;)

Aaron 26.09.2008 01:53

Невыровненный доступ к данным обычно является функцией архитектур CISC, и большинство архитектур RISC не включают ее (ARM, MIPS, PowerPC, Cell). Фактически, чипы самый НЕ являются настольными процессорами для встроенных систем, основанных на количестве чипов, и подавляющее большинство из них являются архитектурами RISC.

Lara Dougan 19.10.2008 05:51

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

Donal Fellows 22.12.2010 10:29

Чтобы быть педантичным, разве стандарт не гарантирует выравнивания, если все члены структуры имеют тип char?

Kerrek SB 15.06.2011 14:20

@Kerrek SB: Стандарт гарантирует выравнивание для любой структуры независимо от используемых типов. Однако для char размером 1 байт невозможно выровнять его. Итак, стандарт гарантирует выравнивание, если все члены структуры являются символами БЕЗ КАКИХ-ЛИБО ЗАПОЛНЕНИЙ.

Bodo Thiesen 26.11.2014 13:25

@LaraDougan: да, и каким-то образом есть простое правило, с помощью которого мы можем понять, почему это так. Стоимость за чип. Чипы x86 для настольных ПК - это потребительские товары за сотни долларов. Нет ничего терпимого для большинства промышленных применений, обычно промышленность имеет дело с чипами стоимостью менее 1 доллара или около того. Легко увидеть, насколько это повлияло на широкое распространение.

v.oddou 14.06.2016 11:41

Почему для первого char есть 3 байта заполнения, а для следующих 2 только 1 байт?

Wayne O 26.01.2017 21:43

@WayneO Количество отступов всегда достаточно, чтобы убедиться, что все, что будет дальше, выровнено в соответствии с его размером. Итак, в X есть 2 байта заполнения после short, чтобы гарантировать, что 4-байтовый int начинается на 4-байтовой границе. В Y после char есть заполнение одним байтом, чтобы убедиться, что 2 байта short начинается на 2-байтовой границе. Поскольку компилятор не может знать, что может быть после структуры в памяти (а это может быть много разных вещей), он готовится к худшему и вставляет достаточно заполнения, чтобы структура стала кратной 4 байтам. X требуется 3 байта для получения 12, Y требуется только 1 для 8.

8bittree 17.02.2017 20:42

«Чипы x86 имеют аппаратную поддержку невыровненного доступа» Верно. «Чипы x86 не вызывают ошибок выравнивания данных» Ложь. Это зависит от инструкции, инструкции SSE, в частности, имеют тенденцию к ошибкам из-за несовпадения (за исключением специальных невыровненных вариантов).

Ben Voigt 25.05.2017 22:20

Существует ли заполнение между двумя структурами, чтобы первый член следующей структуры начинался с выровненного адреса?

AlphaGoku 07.02.2018 07:19

@AkshayImmanuelD: Не "между" структурами, нет, это часть конца структуры. struct {long long a; char b;} обычно имеет 7 байтов заполнения в конце после b, что составляет всего 16 байтов. (на большинстве 64-битных архитектур yada yada)

Mooing Duck 24.01.2019 04:10

Вопрос: если на одной машине в одной программе у меня есть две структуры S1 и S2 с одинаковыми членами. Требует ли стандарт, чтобы обе структуры имели одинаковый размер, или компилятор может решить использовать разные методы заполнения для S1 и S2?

mercury0114 07.11.2020 03:26

@LaraDougan Я не думаю, что это было правдой, когда вы писали свой комментарий, и, вероятно, все еще ложно. Многие встроенные микросхемы не имеют концепции согласованного доступа к данным, потому что они обращаются только к 1 байту за один раз. Многие чипы по-прежнему 8-битные. (Возможно, это уже не большинство чипов, а 8-битные чипы, которых было большинство 12 лет назад).

12431234123412341234123 15.01.2021 00:25

Это возможно, если вы явно или неявно установили выравнивание структуры. Структура, выровненная по 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.

Lakshmi Sreekanth Chitla 26.12.2016 09:07

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

Balázs Börcsök 08.12.2019 02:25

@ BalázsBörcsök Это массивы постоянного размера, поэтому их элементы хранятся непосредственно в структуре с фиксированными смещениями. Компилятор знает все это во время компиляции, поэтому указатель является неявным. Например, если у вас есть структурная переменная этого типа с именем s, тогда &s.a == &s и &s.d == &s + 12 (с учетом выравнивания, показанного в ответе). Указатель сохраняется только в том случае, если массивы имеют переменный размер (например, a был объявлен как char a[] вместо char a[3]), но тогда элементы должны храниться в другом месте.

kbolino 31.03.2020 16:57

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

dolmen 20.08.2013 11:46

- @ Кайл Бертон. Извините, я не понимаю, почему размер структуры someBits равен 4, я ожидаю 8 байтов, поскольку объявлено 2 целых числа (2 * sizeof (int)) = 8 байтов. Благодарность

youpilat13 04.07.2018 18:04

Привет @ youpilat13, :2 и :6 на самом деле указывают 2 и 6 бит, а не полные 32-битные целые числа в этом случае. someBits.x, будучи всего 2 битами, может хранить только 4 возможных значения: 00, 01, 10 и 11 (1, 2, 3 и 4). Имеет ли это смысл? Вот статья об этой функции: geeksforgeeks.org/bit-fields-c

Kyle Burton 13.07.2018 03:44

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

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

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

Так что в основном это вопрос согласования.

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

"веские причины" Пример: обеспечение согласованности двоичной совместимости (заполнения) между 32-битными и 64-битными системами для сложной структуры в демонстрационном коде для проверки концепции, который будет продемонстрирован завтра. Иногда необходимость важнее приличия.

Mr.Ree 08.12.2008 07:58

Все в порядке, кроме тех случаев, когда вы упоминаете операционную систему. Это проблема скорости ЦП, ОС вообще не задействована.

Blaisorblade 12.01.2009 05:51

Еще одна веская причина - если вы вставляете поток данных в структуру, например при разборе сетевых протоколов.

ceo 20.10.2009 19:18

@Blaisorblade Хотя архитектура ЦП является наиболее важным моментом, ОС также может иметь значение. Подумайте о процессоре x86, работающем в реальный режим (MS-DOS) по сравнению с защищенным режимом (Windows, Linux ...).

dolmen 20.08.2013 11:50

@dolmen Я только что указал, что фраза «Операционной системе легче получить доступ к данным» неверна, поскольку ОС не имеет доступа к данным.

Blaisorblade 24.08.2013 21:44

@dolmen На самом деле следует говорить о ABI (бинарном интерфейсе приложения). Выравнивание по умолчанию (используется, если вы не меняете его в исходном коде) зависит от ABI, и многие ОС поддерживают несколько ABI (скажем, 32- и 64-разрядные, или для двоичных файлов из разных ОС, или для разных способов компиляции одинаковые двоичные файлы для той же ОС). OTOH, какое выравнивание удобно с точки зрения производительности, зависит от процессора - доступ к памяти осуществляется одинаково, независимо от того, используете ли вы 32- или 64-разрядный режим (я не могу комментировать реальный режим, но в настоящее время вряд ли актуален для производительности). IIRC Pentium начал отдавать предпочтение 8-байтовому выравниванию.

Blaisorblade 24.08.2013 21:46

__attribute__((packed)) потенциально небезопасен в некоторых случаях: stackoverflow.com/q/8568432/827263

Keith Thompson 10.06.2015 18:41

В компиляторах Microsoft вы должны использовать #pragma pack, делать это с параметром командной строки - зло. (GCC и clang в Windows используют __attribute__, как и в любой другой ОС)

Ben Voigt 31.03.2019 09:02

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

Не совсем. В типичных реализациях к структуре добавляется vtable указатель.

Don Wakefield 18.10.2008 07:16

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

для 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

Ciro Santilli TRUMP BAN IS BAD 04.05.2016 18:39

в C++ есть гарантия порядка: «Нестатические элементы данных (не объединенного) класса, объявленные без промежуточного спецификатора доступа, выделяются таким образом, чтобы более поздние члены имели более высокие адреса в объекте класса»

jfs 10.04.2017 23:40

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

Здесь обсуждаются последствия заполнения структуры, но это не дает ответа на вопрос.

Keith Thompson 10.06.2015 18:39

«... из-за того, что называется упаковкой. ... - я думаю, вы имеете в виду« заполнение ».« Предпочтительный размер большинства современных процессоров, если 32 бита (4 байта) »- это немного упрощенное. Обычно поддерживаются размеры 8, 16, 32 и 64 бита; часто каждый размер имеет собственное выравнивание. И я ' Я не уверен, что ваш ответ добавляет новую информацию, которой еще нет в принятом ответе.

Keith Thompson 12.06.2015 19:02

Когда я сказал об упаковке, я имел в виду, как компилятор упаковывает данные в структуру (и он может делать это, заполняя небольшие элементы, но это не нужно для заполнения, но он всегда упаковывает). Что касается размера - я говорил об архитектуре системы, а не о том, что система будет поддерживать для доступа к данным (что сильно отличается от базовой архитектуры шины). Что касается вашего последнего комментария, я дал упрощенное и расширенное объяснение одного аспекта компромисса (скорость по сравнению с размером) - основной проблемы программирования. Я также описываю способ решения проблемы - этого не было в принятом ответе.

sid1138 13.06.2015 00:12

«Упаковка» в этом контексте обычно относится к более жесткому распределению элементов, чем по умолчанию, как в случае с #pragma pack. Если элементы распределяются по их выравниванию по умолчанию, я бы обычно сказал, что структура упакована нет.

Keith Thompson 13.06.2015 00:16

Упаковка - это своего рода перегруженный термин. Это означает, как вы помещаете элементы структуры в память. Аналогично смыслу складывания предметов в коробку (упаковка для перемещения). Это также означает помещение элементов в память без заполнения (что-то вроде сокращенного обозначения «плотно упаковано»). Затем есть командная версия слова в команде #pragma pack.

sid1138 14.06.2015 00:04

Язык 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: важно.

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 sizeof for a struct equal to the sum of sizeof of each member?"

"Why does the sizeof operator 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.

Это также объект второго по популярности вопроса о заполнении структур, здесь:

«Если нет причин для заполнения, память не будет заполнена». Это бесполезно и вводит в заблуждение. В языке есть определение, и оно не основано на нем. Он принадлежит к разделу типовых / гипотетических реализаций. (Который у вас есть). И тогда это тавтология. (Я понимаю, что это может быть риторическим).

philipxy 12.10.2020 02:18

Другие вопросы по теме