Ограничения выравнивания для malloc()/free()

Старые K&R (2-е изд.) И другие тексты на языке C, которые я читал, обсуждают реализацию динамического распределителя памяти в стиле malloc() а также free() обычно также мимоходом упоминают кое-что об ограничениях выравнивания типов данных. Очевидно, что определенные аппаратные архитектуры компьютера (ЦП, регистры и доступ к памяти) ограничивают способы хранения и адресации определенных типов значений. Например, может быть требование, чтобы 4 байта (long) целое число должно храниться, начиная с адресов, кратных четырем.

Какие ограничения, если таковые имеются, налагают основные платформы (Intel & AMD, SPARC, Alpha) на распределение памяти и доступ к памяти, или я могу безопасно игнорировать выравнивание распределения памяти по конкретным границам адресов?

5 ответов

Sparc, MIPS, Alpha и большинство других "классических архитектур RISC" допускают только согласованный доступ к памяти, даже сегодня. Нераспределенный доступ вызовет исключение, но некоторые операционные системы будут обрабатывать это исключение путем копирования с нужного адреса в программном обеспечении с использованием меньших загрузок и хранилищ. Код приложения не будет знать, что возникла проблема, за исключением того, что производительность будет очень плохой.

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

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

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

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

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

Обратите внимание, что даже на IA-32 и AMD64 для некоторых инструкций / встроенных функций SSE требуются согласованные данные. Эти инструкции выдают исключение, если данные не выровнены, так что, по крайней мере, вам не придется отлаживать ошибки "неправильных данных". Также есть эквивалентные невыровненные инструкции, но, как говорит Дентон, они медленнее.

Если вы используете VC++, кроме директив #pragma pack у вас также есть директивы __declspec(align) для точного выравнивания. В документации VC++ также упоминается функция __aligned_malloc для конкретных требований выравнивания.

Как правило, если вы не перемещаете данные между компиляторами / языками или не используете инструкции SSE, вы, вероятно, можете игнорировать проблемы выравнивания.

Вы все еще должны знать о проблемах выравнивания при размещении класса или структуры в C(++). В этих случаях компилятор сделает правильную вещь для вас, но общий размер структуры / класса может быть более бесполезным, чем необходимо

Например:

struct
{ 
    char A;
    int B;
    char C;
    int D;
};

Будет иметь размер 4 * 4 = 16 байт (предположим, Windows на x86), тогда как

struct
{ 
    char A;
    char C;
    int B;
    int D;
};

Будет иметь размер 4*3 = 12 байт.

Это связано с тем, что компилятор применяет 4-байтовое выравнивание для целых чисел, но только 1 байт для символов.

В общем, объединяйте переменные-члены одного размера (типа) вместе, чтобы минимизировать потерянное пространство.

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

Вы можете увидеть прагматические директивы (в C/C++), которые изменяют выравнивание. Это следует использовать только тогда, когда требуется очень специфическое выравнивание.

// For example, this changes the pack to 2 byte alignment.
#pragma pack(2)
Другие вопросы по тегам