Почему заполнение должно быть мощью двух?

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

#include <stdio.h>

#pragma pack(push, 3)

union aaaa
{

   struct bbb
   {
      int a;
      double b;
      char c;
   }xx;

   float f;
};

#pragma pack(pop)

int main()
{

printf("\n Size: %d", sizeof(union aaaa));

return 0;
}

При компиляции

warning: alignment must be a small power of two, not 3 [-Wpragmas]
warning: #pragma pack (pop) encountered without matching #pragma pack (push) [-Wpragmas]

Кажется, что #pragma не имеет никакого эффекта. Выход только 24. то есть 4 байт выровнены.

Ответы

Ответ 1

Короткий ответ заключается в том, что базовые объекты в процессорах имеют размеры, которые являются малыми степенями двух (например, 1, 2, 4, 8 и 16 байт), а память организована в группы, размер которых малой мощности двух ( например, 8 байтов), поэтому структуры должны быть выровнены, чтобы хорошо работать с этими размерами.

Долгий ответ заключается в том, что причины этого основаны на физике и элементарной математике. Компьютеры, естественно, работают с битами со значениями 0 и 1. Это связано с тем, что легко создавать физические вещи, которые переключаются между двумя значениями: высокое напряжение и низкое напряжение, наличие заряда или отсутствие заряда и т.д. Отличие между тремя значениями сложнее, потому что вы должны быть более чувствительными к переходам между значениями. Таким образом, поскольку компьютерные технологии развивались на протяжении десятилетий, мы использовали биты (двоичные цифры) вместо альтернатив, таких как цифровые цифры.

Чтобы сделать большие числа, мы объединяем несколько бит. Таким образом, два бита могут, объединены, иметь четыре значения. Три бита могут иметь восемь значений и т.д. В старых компьютерах иногда бит составлял шесть или десять групп одновременно. Тем не менее, восемь стали обычными, и в настоящее время они являются стандартными. Использование восьми бит для байта не имеет такой сильной физической причины, как некоторые другие группы, которые я описываю, но это путь мира.

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

Процессор использует биты для значений, поэтому он будет использовать биты для значений адреса. Таким образом, память будет построена так, чтобы принимать биты, чтобы указать, какие байты будут поставляться процессору при чтении процессора или какие байты хранить при записи процессора. Что делает устройство памяти с этими битами? Легко использовать один бит для управления одним коммутатором путей в память. Память будет сделана из множества мелких частей, в которых хранятся байты.

Рассмотрим вещь в устройстве памяти, которая может хранить байт, и рассмотрите две из этих вещей рядом друг с другом, скажем A и B. Мы можем использовать переключатель, чтобы выбрать, хотим ли мы, чтобы байт был активным, или B байт. Теперь рассмотрим четыре из этих вещей: A, B, C и D. Мы можем использовать один переключатель, чтобы выбрать, использовать ли группу A-B или использовать группу C-D. Затем другой переключатель выбирает A или B (если используется группа A-B) или C или D (если используется группа C-D).

Этот процесс продолжается: каждый бит в адресе памяти выбирает группу используемых единиц хранения. 1 бит выбирает между двумя запоминающими устройствами, 2 выбирает между 4, 3 выбирает между 8, 4 выбирает между 16 и так далее. 8 бит выбирают между 256 единицами хранения, 24 бита выбирают между 16 777 216 единицами хранения и 32 битами выбирают между 4 294 967 296 единицами хранения.

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

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

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

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

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

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

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

Ответ 2

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

Тем не менее, если по какой-то странной причине вам требуется произвольное выравнивание, ничто не мешает вам добавлять фиктивные массивы char в нужные места, чтобы обеспечить выравнивание (что более или менее то, что делает компилятор под капотом).

Ответ 3

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

Трехбитное поле типа

[0][0][0]

имеет возможное представление восьми чисел

0, 1, 2, 3, 4, 5, 6, 7, and 8

Если вы ограничились цифрами

0, 1, 2

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

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

Требование "власти двух" было побочным эффектом архитектуры шины в сочетании с простой процедурой, гарантирующей, что структуры данных C могут быть выровнены с выровненными границами доступа.

Ответ 4

В то время как есть физические причины, что компьютерные проекты предпочитают выравнивание памяти из двух источников памяти, еще одним важным требованием является то, что все выравнивания памяти должны равномерно разделяться на некоторое число. В противном случае, если, например, одна структура должна была быть выровнена на кратное 7, нужно было выровнять по кратным 8, а нужно было выровнять по краю 9, a union, содержащее все три структуры, необходимо было бы выровнять с кратное 504.

Обычный способ выполнения требования делимости состоит в том, чтобы сказать, что все размеры выравнивания должны подразделяться на две большие мощности. Такой подход будет работать, но это будет не единственная возможная реализация. Можно было бы, если бы было так наклонно, проектировать аппаратное обеспечение, которое будет работать с 120-байтовыми линиями кэша, и позволять выравнивать объекты по кратным 2, 3, 4, 5, 6, 8, 10, 12, 15, 20, 24, 30, 40, 60 или 120 байт. Такой дизайн может быть очень разумным с точки зрения аппаратного обеспечения (каждая строка кэша будет храниться как 1024 бита, включая 64 бита исправления ошибок, метки записи или другую информацию) и позволит эффективно хранить 80-битные реалы, RGB или XYZ (любого числового типа, включая 80-битные реалы). Если кто-то не требовал, чтобы физические адреса были в той же числовой последовательности, что и логические адреса, схема, требуемая для 120-байтовых карт с картами для кэширования строк, не была бы слишком дорогостоящей. С другой стороны, вне специализированных приложений маловероятно, чтобы выгоды от сопоставлений без полномочий двух были достаточными для преодоления проблем стоимости и инерции рынка.

Ответ 5

В соответствии с документацией GCC (http://gcc.gnu.org/onlinedocs/gcc/Structure_002dPacking-Pragmas.html) используется папка pragma "Для совместимости с компиляторами Microsoft Windows".

Если вы ищете документацию по выравниванию по MS (http://msdn.microsoft.com/en-us/library/ms253949(v=vs.80).aspx), вы обнаружите, что компилятор MSVC обеспечивает выравнивание типов на основе размера данных; который, как объясняется другими сообщениями, логически всегда имеет степень 2.

Если ваша проблема заключается в том, что вам нужно какое-то причудливое выравнивание структуры данных (для доступа к некорректному сопоставленному периферийному устройству с памятью), то лучше всего использовать упаковку структуры manual, добавив поля, которые добавляют требуемое дополнение.