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

Я не мог найти достойный документ, который объясняет, как работает система выравнивания, и почему некоторые типы более строго выровнены, чем другие.

Ответы

Ответ 1

Я попытаюсь объяснить кратко.

Что такое выравнивание данных?

Архитектура вашего компьютера состоит из процессора и памяти. Память организована в ячейках, поэтому:

 0x00 |   data  |  
 0x01 |   ...   |
 0x02 |   ...   |

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

Когда вы определяете переменную в своей программе на C/С++, одна или несколько разных ячеек заняты вашей программой.

Например

int variable = 12;

Предположим, что каждая ячейка содержит 32 бита, а размер типа int - 32 бита, а затем где-то в вашей памяти:

variable: | 0 0 0 c |  // c is hexadecimal of 12.

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

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

Например, у меня есть две разные части данных для хранения (я собираюсь использовать "строковое представление, чтобы сделать более понятным" ):

data1: "ab"
data2: "cdef"

Итак, память будет составлена ​​таким образом (2 разных ячейки):

|a b c d|     |e f 0 0|

То есть data1 занимает только половину ячейки, поэтому data2 занимает оставшуюся часть и часть второй ячейки.

Теперь предположим, что CPU хочет читать data2. Для доступа к данным для ЦП требуется 2 такта, потому что в течение одного часа он считывает первую ячейку и в течение других часов считывает оставшуюся часть во второй ячейке.

Если мы выровняем data2 в соответствии с этим примером памяти, мы можем введите вид заполнения и сдвига data2 во второй ячейке.

|a b 0 0|     |c d e f|
     ---
   padding

Таким образом, процессор будет потерять только "1 такт", чтобы получить доступ к data2.

Что делает система выравнивания

Система выравнивания просто вводит это дополнение для выравнивания данных с памятью системы, помните в соответствии с архитектурой. Когда данные выровнены в памяти, вы не теряете процессорные циклы для доступа к данным.

Это делается по соображениям производительности (99% раз).

Ответ 2

Это "реализация определена", то есть требования к выравниванию не являются частью спецификации языка.

У разных ЦП разные требования к выравниванию. Некоторые из них не могли адресовать 16-битное значение на неровном адресе, некоторые могли бы. Некоторые из них не могли адресовать значение с плавающей запятой, если оно не соответствует адресу, делящемуся по его размеру, некоторые могут. И так далее. Некоторые из них будут получать доступ к смещенным объектам данных медленнее, чем правильно выровненные, другие могут отключиться от несвязанного доступа.

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

Если вы указате указатели на тип, вы можете заставить код адресовать данный объект по адресу, где он не может быть адресован. Вы должны убедиться, что требования к выравниванию "старого" типа не менее строгие, чем требования "нового" типа.

В С++ (С++ 11 вверх) вы получаете оператор alignof, чтобы сообщить вам требования к выравниванию заданного тип. Вы также получаете оператор alignas для обеспечения более строгого выравнивания по заданному типу или объекту.

В C (C11 вверх) вы получаете _Alignof и _Alignas, которые <stdalign.h> обертываются в alignof/alignas макросы удобства. (Спасибо, Лундин - C11 не моя сильная сторона.)

Ответ 3

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

Это приводит к ограничению языка.

Рассмотрим код (плохой):

uint8_t a[] = {1,2,3,4,5,6};
uint32_t b = *(uint32_t*)&a[1];

и предположим, что a выровнена с делимой на четыре границы. Затем вторая строка пытается прочитать слово из адреса его второго элемента, то есть адреса, не делящегося на четыре. Это приведет к ошибке выравнивания. Но в C это просто запрещено строгим правилом псевдонимов.