Предисловие: Провел свое исследование о выравнивании структур. Посмотрел вопрос это, один это, а также один это - но так и не нашел своего ответа.
Мой актуальный вопрос:
Вот фрагмент кода, который я создал, чтобы прояснить свой вопрос:
#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? Что вы ожидаете от int и float без double и почему?
@Gerhardh - поменял порядок и получил 24! Что касается моих ожиданий, я ожидал выравнивания по самому большому элементу - поэтому 8 байтов * 3 элемента = 24. Int и Float имеют одинаковый размер (4), поэтому 8 в порядке, IMO.
Вы должны отличать выравнивание всей конструкции от выравнивания каждого элемента.
@Gerhardh Не могли бы вы объяснить это поподробнее?
Ответ от ClsForcookies касается этой темы. Выравнивание структуры основано на самом большом члене: 8 байтов. Но внутри структуры каждому члену требуется выравнивание только для его собственного типа => 4 байта для float.
К вашему сведению, алгоритм, с помощью которого компиляторы могут разложить структуру и определить ее размер, - это здесь.
@Gerhardh: Выравнивание структуры основано на строжайшем выравнивании ее членов, а не на самом большом члене. Например, структура может иметь восьмибайтовый double, который требует четырехбайтового выравнивания, и 20-байтовый массив char, который требует только однобайтового выравнивания, и структура должна быть выровнена по кратному четырем байтам, а не один или 20. (Или, если бы мы хотели рассмотреть теоретические экзотические архитектуры, требование выравнивания структуры было бы наименьшим общим кратным требований выравнивания ее членов.)
Мне нравится ваш список ссылок, которые вы изучили, вместо того, чтобы писать просто «Я все прочитал». как и многие другие вопросы. Хорошая работа.
@MordechayS если вы считаете, что получили хороший ответ, вы должны принять его
@alinsoar Все еще пытаюсь найти подходящее время, чтобы прочитать все замечательные комментарии и ответы, а затем выбрать. Спасибо!





На вашей платформе справедливо следующее: Размер 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)Таким образом, нет причин запускать какие-либо члены позже, поэтому вся структура отлично умещается в 16 байтов.
(Note) Это не совсем так: для каждого из них мы знаем, что и размер, и выравнивание <= N, что N кратно требованию выравнивания, и что не существует N1 <N, для которого это также было бы. Однако это очень мелкая деталь, и для ясности ответ просто предполагает, что фактические требования к размеру и выравниванию для примитивных типов являются неопределенными, что в любом случае является наиболее вероятным случаем на платформе OP.
Но разве компилятор не выравнивает элементы все по одинаковому размеру (8)? Я думал, что int тоже нужно выровнять до 8!
@MordechayS Почему так должно быть? sizeof эффективно сообщает вам о требованиях к выравниванию. Я расширил ответ.
@Angew sizeof сообщает требование выравнивания только для примитивных типов
@Angew: sizeof не сообщает о требованиях к выравниванию. Процессор может иметь восьмибайтовые объекты double, но требует, чтобы они были выровнены по четырем байтам. (Это может произойти из-за того, что процессор имеет только четырехбайтовые обращения к памяти, поэтому ему нужно использовать два доступа для загрузки / сохранения двойника, независимо от того, равен ли его адрес 0 по модулю 8 или 4 по модулю 8, поэтому ему все равно, какой из это правда.) Требование выравнивания объекта сообщается оператором _Alignof.
@EricPostpischil Достаточно честно, поэтому я квалифицировал комментарий как «эффективно». Спорный вопрос, гарантирует ли кажущийся уровень вопроса такую точную точность, но я добавил сноску к ответу.
Компилятор вставит заполнение, чтобы гарантировать, что каждый элемент находится со смещением, кратным его размеру.
В этом случае int будет со смещением = 0 (относительно адреса экземпляра структуры), float со смещением = 4 и double со смещением = 8, поскольку размеры int и float в сумме составляют 8.
В конце нет отступов - размер структуры уже равен 16, что кратно размеру double.
Процессоры не всегда требуют, чтобы объект располагался по адресу, кратному его размеру. Кажется, что это справедливо в конкретном случае, о котором спрашивает OP, но процессор может иметь восьмибайтовые объекты double, которым требуется только четырехбайтовое выравнивание, или может иметь десятибайтовые объекты, требующие 16-байтового выравнивания.
@EricPostpischil: Конечно. Есть и другая возможность: выравнивание не требуется, но предпочтительнее выравнивание доступа. Например, если предположить, что OP нацелен на x86 / 64, MOVSD, используемый для загрузки double, будет дороже, если адрес не выровнен, но все же возможно.
Ваша структура должна быть длиной 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)?
примитивы должны быть выровнены по размеру, в то время как структуры должны быть выровнены по размеру их самого большого члена (вместо их собственного размера, то есть в базовом случае - выравнивание до 8 вместо 16)
Не могли бы вы подробнее рассказать о выравнивании структур и элементов?
Позвольте мне уточнить: если выравнивание используется для производительности / простоты - почему мы разрешаем помещать элемент в позицию, отличную от 8-фактора? (как элемент float в базовом случае)
1) производительность / простота имеют компромисс с использованием памяти. Мы можем разрешить только 8-байтовое выравнивание для чисел с плавающей запятой для простоты, но это будет стоить дороже. 2) По поводу того, почему - не уверен. Можно предположить, что это считалось правильным способом упаковать удар в память - вы разрезаете его на куски для вашего самого большого типа (8-байтов), и каждый кусок вы заполняете как можно лучше (2 4 байта / 1 4- byte + 4-padding и так далее ...)
Возможно, вы захотите отредактировать свои комментарии к своему вопросу, надеюсь, у кого-то еще будет лучший ответ
Прежде всего, C не требует какого-либо выравнивания для элементов внутри структуры. Вот почему трудно сказать, какое значение возвращает sizeof () для экземпляра какой-либо структуры.
Единственное ограничение, которое C накладывает на элементы структуры, состоит в том, что адрес, выделенный для первого члена, должен быть кратным его выравниванию (так, чтобы к вектору этой структуры можно было получить доступ как указатель + sizeof () * index). В вашем случае IntFloatAndDoubleStruct должен быть выровнен по кратному размеру sizeof (int), поскольку a имеет тип int.
Внутренним членам в некоторых случаях выделяются адреса, кратные их выравниванию, чтобы работать быстрее (поэтому вставляются некоторые отступы), но это не налагается C. Таким образом, компилятор может решить вставить #pragma, чтобы изменить алгоритм, используемый для выделения конструкции. См. здесь.
Как предполагает CIsForCookies, структура будет выровнена с кратным максимальным выравниванием, чтобы вектор таких структур по-прежнему выровнял каждый член. Но это не налагается языком C, а с учетом производительности реализации, потому что в addr_struct[i]+member_offset оба термина делятся выравниванием.
Измените порядок чисел с плавающей запятой и удвоения, и вы должны получить 24.