Выравнивания по 4 байта

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

Предположим, у меня есть произвольный указатель:

unsigned char* ptr

теперь я пытаюсь получить двойное значение из памяти местонахождение:

double d = **((double*)ptr);

это вызовет проблемы?

9 ответов


Это определенно может вызвать проблемы в некоторых системах.

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


вот что Intel x86/x64 справочное руководство говорит о alignments:

4.1.1 выравнивание слова, двойные слова, Quadwords, и дважды Quadwords

слова, doublewords и quadwords делают не нужно выравнивать в памяти на урочище. Естествознание границы для слов, двойные слова, и quadwords являются четные адреса, адреса делится на четыре, и адреса равномерно делимый к восьми, соответственно. Однако, улучшить представление программы, структуры данных (особенно стеки) должны быть выровнены по естественным границы, когда это возможно. Этот причина этого в том, что процессор требуется два доступа к памяти, чтобы сделать выровненный доступ к памяти; выровненный для доступа требуется только одна память доступ. Операнд слова или двойного слова который пересекает 4-байтовую границу или операнд quadword, который пересекает Рассматривается 8-байтовая граница невыровненные и требуется два отдельных циклы шины памяти для доступа.

некоторые инструкции, которые действуют на двойные quadwords требуют памяти операнды для выравнивания по натуральному граница. Эти инструкции генерируют исключение общей защиты (#GP) если указан несогласованный операнд. Естественная граница для двойника четверных-это любой адрес, равномерно делится на 16. Другая инструкция которые работают на двойных quadwords разрешить неограниченный доступ (без создание общая защита исключение.) Однако, дополнительная память циклы шины необходимы для того чтобы достигнуть несогласованные данные из памяти.

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


Да, это может вызвать ряд проблем. Стандарт C++ фактически не гарантирует, что он будет работать. Вы не можете просто произвольно выбирать между типами указателей.

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

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

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

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


выравнивание влияет на компоновку структур. Рассмотрим эту структуру:

struct S {
  char a;
  long b;
};

на 32-битном процессоре макет этой структуры часто будет:

a _ _ _ b b b b

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

struct S {
  char a;
  short b;
  long c;
};

макет будет такая:

a _ b b c c c c

16-разрядное значение выравнивается по 16-разрядной границе.

иногда хочется упаковка в структуры возможно, если вы хотите сопоставить структуру с форматом данных. Используя параметр компилятора или, возможно,#pragma вы можете удалить лишнее пространство:

a b b b b
a b b c c c c

однако доступ к несогласованному члену упакованной структуры часто будет намного медленнее на современных процессорах или может даже привести к исключению.


Да, это может вызвать проблемы.

4-выравнивание просто означает, что указатель, рассматриваемый как числовой адрес, кратен 4. Если указатель не кратен требуемому выравниванию, то он не выровнен. Существует две причины, по которым компиляторы устанавливают ограничения выравнивания для определенных типов:

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

если вы в случае (1), а double-4-выровнен, и вы попробуете свой код с char * указатель, который не выровнен по 4, тогда вы, скорее всего, получите аппаратную ловушку. Некоторые аппаратные средства не ловушка. Он просто загружает бессмысленное значение и продолжает. Однако стандарт C++ не определяет, что может произойти( неопределенное поведение), поэтому этот код может установить компьютер горит.

на x86 вы никогда не в случае (1), потому что стандартные инструкции загрузки могут обрабатывать несогласованные указатели. На ARM нет несогласованных нагрузок, и если вы попытаетесь, то ваша программа аварийно завершит работу (если Вам ПОВЕЗЕТ. Некоторые руки молча отказывают).

возвращаясь к вашему примеру, вопрос, почему вы пытаетесь это char * это не 4-выровнено. Если вы успешно написали double там через double *, потом вы сможете прочитать его обратно. Так что если у вас изначально был" правильный " указатель на double, который вы бросили в char * и теперь вы отбрасываете назад, вам не нужно беспокоиться о выравнивании.

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

самый простой способ сделать это-убедиться, что Вы читаете данные файла в подходящую структуру. Вам также помогает тот факт, что выделения памяти всегда выровнены по максимальному требованию выравнивания любого типа, который они достаточно велики, чтобы содержать. Поэтому, если вы выделяете буфер достаточно большой, чтобы содержать double, то начало этого буфера имеет любое выравнивание требуется двойной. Таким образом, вы можете прочитать 8 байтов, представляющих double в начале буфера, cast (или использовать объединение) и прочитать double out.

кроме того, вы можете сделать что-то вроде этого:

double readUnalignedDouble(char *un_ptr) {
    double d;
    // either of these
    std::memcpy(&d, un_ptr, sizeof(d));
    std::copy(un_ptr, un_ptr + sizeof(d), reinterpret_cast<char *>(&d));
    return d;
}

это гарантированно будет действительным (предполагая, что un_ptr действительно указывает на байты действительного двойного представления для вашей платформы), потому что double является POD и, следовательно, может быть скопирован байт за байтом. Это может быть не самое быстрое решение, если у вас много удваивается для загрузки.

если Вы читаете из файла, на самом деле это немного больше, чем если вы беспокоитесь о платформах с двойными представлениями без IEEE или с 9-битными байтами или некоторыми другими необычными свойствами, где могут быть биты без значения в сохраненном представлении двойника. Но вы на самом деле не спрашивали о файлах, я просто сделал это в качестве примера, и в любом случае эти платформы намного реже, чем проблема, о которой вы спрашиваете, которая для double to имейте требование выравнивания.

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


на x86 он всегда будет работать, конечно, более эффективно при выравнивании.

Но если вы МНОГОПОТОЧНЫ, то следите за чтением-записью-разрывом. С 64-разрядным значением вам нужна машина x64, чтобы дать вам атомарное чтение и запись между потоками.
Если, скажем, Вы читаете значение из другого потока, когда оно, скажем, увеличивается между 0x00000000.FFFFFFFF и 0x00000001.00000000, то другой поток может теоретически прочитать, скажем, 0 или 1FFFFFFFF, особенно если сказать значение Оседлал границу кэш-линии.
Я рекомендую "параллельное программирование на Windows" Даффи для его приятного обсуждения моделей памяти, даже упоминая выравнивание gotchas на мультипроцессорах, когда dot-net делает GC. Ты хочешь держаться подальше от Итаниума !


SPARC (Solaris machines) - это еще одна архитектура (по крайней мере, в прошлом), которая задохнется (даст ошибку SIGBUS), если вы попытаетесь использовать несогласованное значение.

добавление к Мартину Йорку, malloc также выровнено по максимально возможному типу, т. е. безопасно для всего, например, "новое". На самом деле, часто "Новый" просто использует malloc.


примером требования к aligment является использование инструкций векторизации (SIMD). (Его можно использовать без aligment но гораздо быстре если вы используете вид инструкции которая требует выравнивания).


принудительное выравнивание памяти гораздо чаще встречается в RISC основанные архитектуры, такие как MIPS.
Основное мышление для этих типов процессоров, AFAIK, действительно является проблемой скорости.
Методология RISC заключалась в том, чтобы иметь набор простых и быстрых инструкций ( обычно один цикл памяти на инструкцию ). Это не обязательно означает, что у него меньше инструкций, чем у процессора CISC, более простые и быстрые инструкции.
Многие процессоры MIPS, хотя 8 байт addressable будет выровнен по словам (32-бит обычно, но не всегда), затем маскируйте соответствующие биты.
Идея заключается в том, что это быстрее сделать выровненную нагрузку + битовую маску, чем пытаться сделать несогласованную нагрузку. Как правило ( и, конечно, это действительно зависит от набора микросхем ), выполнение не выровненной нагрузки приведет к ошибке шины, поэтому процессоры RISC будут предлагать инструкцию "unaligned load / store", но это часто будет намного медленнее, чем соответствующая выровненная загрузить / сохранить.

конечно, это все еще не отвечает на вопрос, почему они это делают.e какое преимущество дает вам выравнивание слов памяти? Я не специалист по оборудованию, и я уверен, что кто-то здесь может дать лучший ответ, но мои две лучшие догадки:
1. Это может быть намного быстрее, чтобы получить из кэша при выравнивании word, потому что многие кэши организованы в кэш-строки (что-нибудь от 8 до 512 байт ) и как кэш-память, как правило, намного дороже, чем ОЗУ, вы хочу извлечь из этого максимум пользы.
2. Доступ к каждому адресу памяти может быть намного быстрее, так как он позволяет вам читать через "пакетный режим" (i.e получение следующего последовательного адреса до его необходимости)

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