Sizeof (), выравнивание в C-структурах:

Предисловие: Провел свое исследование о выравнивании структур. Посмотрел вопрос это, один это, а также один это - но так и не нашел своего ответа.

Мой актуальный вопрос:

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

#include "stdafx.h"
#include <stdio.h>

struct IntAndCharStruct
{
    int a;
    char b;
};

struct IntAndDoubleStruct
{
    int a;
    double d;
};

struct IntFloatAndDoubleStruct
{
    int a;
    float c;
    double d;
};

int main()
{
    printf("Int: %d\n", sizeof(int));
    printf("Float: %d\n", sizeof(float));
    printf("Char: %d\n", sizeof(char));
    printf("Double: %d\n", sizeof(double));
    printf("IntAndCharStruct: %d\n", sizeof(IntAndCharStruct));
    printf("IntAndDoubleStruct: %d\n", sizeof(IntAndDoubleStruct));
    printf("IntFloatAndDoubleStruct: %d\n", sizeof(IntFloatAndDoubleStruct));
    getchar();
}

И это вывод:

Int: 4
Float: 4
Char: 1
Double: 8
IntAndCharStruct: 8
IntAndDoubleStruct: 16
IntFloatAndDoubleStruct: 16

Я вижу выравнивание, наблюдаемое в IntAndCharStruct и в IntAndDoubleStruct.

Но я просто не получаю IntFloatAndDoubleStruct.

Проще говоря: Почему не sizeof(IntFloatAndDoubleStruct) = 24?

Заранее спасибо!

p.s: Я использую Visual-Studio 2017, стандартное консольное приложение.

Редактировать: Согласно комментариям, протестировал IntDoubleAndFloatStruct (разный порядок элементов) и получил 24 в sizeof() - и я буду рад, если в ответах будет отмечен и объяснен и этот случай.

Измените порядок чисел с плавающей запятой и удвоения, и вы должны получить 24.

Gerhardh 12.06.2018 12:58

Почему вы ожидали получить 24? Что вы ожидаете от int и float без double и почему?

Gerhardh 12.06.2018 13:00

@Gerhardh - поменял порядок и получил 24! Что касается моих ожиданий, я ожидал выравнивания по самому большому элементу - поэтому 8 байтов * 3 элемента = 24. Int и Float имеют одинаковый размер (4), поэтому 8 в порядке, IMO.

MordechayS 12.06.2018 13:05

Вы должны отличать выравнивание всей конструкции от выравнивания каждого элемента.

Gerhardh 12.06.2018 13:11

@Gerhardh Не могли бы вы объяснить это поподробнее?

MordechayS 12.06.2018 13:16

Ответ от ClsForcookies касается этой темы. Выравнивание структуры основано на самом большом члене: 8 байтов. Но внутри структуры каждому члену требуется выравнивание только для его собственного типа => 4 байта для float.

Gerhardh 12.06.2018 13:18

К вашему сведению, алгоритм, с помощью которого компиляторы могут разложить структуру и определить ее размер, - это здесь.

Eric Postpischil 12.06.2018 13:27

@Gerhardh: Выравнивание структуры основано на строжайшем выравнивании ее членов, а не на самом большом члене. Например, структура может иметь восьмибайтовый double, который требует четырехбайтового выравнивания, и 20-байтовый массив char, который требует только однобайтового выравнивания, и структура должна быть выровнена по кратному четырем байтам, а не один или 20. (Или, если бы мы хотели рассмотреть теоретические экзотические архитектуры, требование выравнивания структуры было бы наименьшим общим кратным требований выравнивания ее членов.)

Eric Postpischil 12.06.2018 13:28

Мне нравится ваш список ссылок, которые вы изучили, вместо того, чтобы писать просто «Я все прочитал». как и многие другие вопросы. Хорошая работа.

Yunnosch 12.06.2018 13:37
Правильный спецификатор формата для sizeof() - "%zu".
Andrew Henle 12.06.2018 14:34

@MordechayS если вы считаете, что получили хороший ответ, вы должны принять его

alinsoar 13.06.2018 09:13

@alinsoar Все еще пытаюсь найти подходящее время, чтобы прочитать все замечательные комментарии и ответы, а затем выбрать. Спасибо!

MordechayS 13.06.2018 09:16
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
7
12
1 228
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

На вашей платформе справедливо следующее: Размер int и float равен 4. Требование к размеру и выравниванию для double составляет 8.

Мы знаем это из показанных вами выходных данных sizeof. sizeof (T) дает количество байтов между адресами двух последовательных элементов типа T в массиве. Итак, мы знаем, что требования к выравниванию такие, как я сказал выше. (Note)

Теперь компилятор сообщил 16 для IntFloatAndDoubleStruct. Получается?

Предположим, у нас есть такой объект по адресу, выровненному по 16.

  • Таким образом, int a находится по адресу X, выровненному по 16, так что он выровнен по 4 очень хорошо. Он будет занимать байты [X, X + 4)
  • Это означает, что float c может начинаться с X + 4, который выровнен по 4, что нормально для float. Он будет занимать байты [X + 4, X + 8)
  • Наконец, double d может начинаться с X + 8, который выровнен по 8, что нормально для double. Он будет занимать байты [X + 8, X + 16)
  • Это оставляет X + 16 свободным для следующего объекта структуры, снова выровненного по 16.

Таким образом, нет причин запускать какие-либо члены позже, поэтому вся структура отлично умещается в 16 байтов.


(Note) Это не совсем так: для каждого из них мы знаем, что и размер, и выравнивание <= N, что N кратно требованию выравнивания, и что не существует N1 <N, для которого это также было бы. Однако это очень мелкая деталь, и для ясности ответ просто предполагает, что фактические требования к размеру и выравниванию для примитивных типов являются неопределенными, что в любом случае является наиболее вероятным случаем на платформе OP.

Но разве компилятор не выравнивает элементы все по одинаковому размеру (8)? Я думал, что int тоже нужно выровнять до 8!

MordechayS 12.06.2018 13:09

@MordechayS Почему так должно быть? sizeof эффективно сообщает вам о требованиях к выравниванию. Я расширил ответ.

Angew is no longer proud of SO 12.06.2018 13:22

@Angew sizeof сообщает требование выравнивания только для примитивных типов

Tyker 12.06.2018 13:23

@Angew: sizeof не сообщает о требованиях к выравниванию. Процессор может иметь восьмибайтовые объекты double, но требует, чтобы они были выровнены по четырем байтам. (Это может произойти из-за того, что процессор имеет только четырехбайтовые обращения к памяти, поэтому ему нужно использовать два доступа для загрузки / сохранения двойника, независимо от того, равен ли его адрес 0 по модулю 8 или 4 по модулю 8, поэтому ему все равно, какой из это правда.) Требование выравнивания объекта сообщается оператором _Alignof.

Eric Postpischil 12.06.2018 13:25

@EricPostpischil Достаточно честно, поэтому я квалифицировал комментарий как «эффективно». Спорный вопрос, гарантирует ли кажущийся уровень вопроса такую ​​точную точность, но я добавил сноску к ответу.

Angew is no longer proud of SO 12.06.2018 13:40

Компилятор вставит заполнение, чтобы гарантировать, что каждый элемент находится со смещением, кратным его размеру.

В этом случае int будет со смещением = 0 (относительно адреса экземпляра структуры), float со смещением = 4 и double со смещением = 8, поскольку размеры int и float в сумме составляют 8.

В конце нет отступов - размер структуры уже равен 16, что кратно размеру double.

Процессоры не всегда требуют, чтобы объект располагался по адресу, кратному его размеру. Кажется, что это справедливо в конкретном случае, о котором спрашивает OP, но процессор может иметь восьмибайтовые объекты double, которым требуется только четырехбайтовое выравнивание, или может иметь десятибайтовые объекты, требующие 16-байтового выравнивания.

Eric Postpischil 12.06.2018 13:20

@EricPostpischil: Конечно. Есть и другая возможность: выравнивание не требуется, но предпочтительнее выравнивание доступа. Например, если предположить, что OP нацелен на x86 / 64, MOVSD, используемый для загрузки double, будет дороже, если адрес не выровнен, но все же возможно.

joe_chip 12.06.2018 18:54
Ответ принят как подходящий

Ваша структура должна быть длиной 8*N байта, поскольку она имеет член с 8 байтами (double). Это означает, что структура находится в памяти по адресу (A), кратному 8 (A%8 == 0), и ее конечный адрес будет (A + 8N), который также будет делиться на 8.

Оттуда вы сохраняете 2 4-байтовые переменные (int + float), что означает, что теперь вы занимаетесь областью памяти [A,A+8). Теперь вы храните 8-байтовую переменную (double). Начиная с (A+8) % 8 == 0 [начиная с A%8 == 0], нет необходимости в заполнении. Итак, без прокладки вы получите 4+4+8 == 16.

Если вы измените порядок на int -> double -> float, вы займете 24 байта, поскольку исходный адрес переменной double не будет делиться на 8, и ему нужно будет заполнить 4 байта, чтобы добраться до действительного адреса (а также структура будет иметь заполнение в конце ).


|--------||--------||--------||--------||--------||--------||--------||--------|
|   each ||   cell ||  here  ||represen||-ts  4  || bytes  ||        ||        |
|--------||--------||--------||--------||--------||--------||--------||--------|

A        A+4       A+8      A+12      A+16      A+20      A+24                      [addresses]
|--------||--------||--------||--------||--------||--------||--------||--------|    
|   int  ||  float || double || double ||        ||        ||        ||        |    [content - basic case]
|--------||--------||--------||--------||--------||--------||--------||--------|

first padding to ensure the double sits on address that is divisble by 8
last  padding to ensure the struct size is divisble by the largest member's size (8)
|--------||--------||--------||--------||--------||--------||--------||--------|    
|   int  || padding|| double || double || float  || padding||        ||        |    [content - change order case]
|--------||--------||--------||--------||--------||--------||--------||--------|

Прежде всего, только что дочитали ответ - много узнали, спасибо! Но у меня все еще есть вопрос: в «базовом случае» - почему нам не нужно, чтобы элемент float начинался с адреса, делимого на самый большой элемент структуры (8)?

MordechayS 13.06.2018 09:54

примитивы должны быть выровнены по размеру, в то время как структуры должны быть выровнены по размеру их самого большого члена (вместо их собственного размера, то есть в базовом случае - выравнивание до 8 вместо 16)

CIsForCookies 13.06.2018 10:18

Не могли бы вы подробнее рассказать о выравнивании структур и элементов?

MordechayS 13.06.2018 10:40

Позвольте мне уточнить: если выравнивание используется для производительности / простоты - почему мы разрешаем помещать элемент в позицию, отличную от 8-фактора? (как элемент float в базовом случае)

MordechayS 13.06.2018 10:43

1) производительность / простота имеют компромисс с использованием памяти. Мы можем разрешить только 8-байтовое выравнивание для чисел с плавающей запятой для простоты, но это будет стоить дороже. 2) По поводу того, почему - не уверен. Можно предположить, что это считалось правильным способом упаковать удар в память - вы разрезаете его на куски для вашего самого большого типа (8-байтов), и каждый кусок вы заполняете как можно лучше (2 4 байта / 1 4- byte + 4-padding и так далее ...)

CIsForCookies 13.06.2018 11:22

Возможно, вы захотите отредактировать свои комментарии к своему вопросу, надеюсь, у кого-то еще будет лучший ответ

CIsForCookies 13.06.2018 11:24
не могу добавить это к предыдущему комментарию ... 3) strcut vs. element - не вижу разницы. Оба выровнены внешне по размеру. Внутри структура пытается поддерживать выравнивание с минимальным заполнением, как обычно.
CIsForCookies 13.06.2018 11:30

Прежде всего, C не требует какого-либо выравнивания для элементов внутри структуры. Вот почему трудно сказать, какое значение возвращает sizeof () для экземпляра какой-либо структуры.

Единственное ограничение, которое C накладывает на элементы структуры, состоит в том, что адрес, выделенный для первого члена, должен быть кратным его выравниванию (так, чтобы к вектору этой структуры можно было получить доступ как указатель + sizeof () * index). В вашем случае IntFloatAndDoubleStruct должен быть выровнен по кратному размеру sizeof (int), поскольку a имеет тип int.

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

Как предполагает CIsForCookies, структура будет выровнена с кратным максимальным выравниванием, чтобы вектор таких структур по-прежнему выровнял каждый член. Но это не налагается языком C, а с учетом производительности реализации, потому что в addr_struct[i]+member_offset оба термина делятся выравниванием.

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