что такое "выравнивание по стеку"? - PullRequest
42 голосов
/ 23 марта 2009

Что такое выравнивание стека? Почему это используется? Можно ли управлять настройками компилятора?

Подробности этого вопроса взяты из проблемы, с которой сталкиваются при попытке использовать библиотеки ffmpeg с msvc, однако меня действительно интересует объяснение того, что такое «выравнивание стека».

Подробности:

  • Когда я запускаю MSVC-программу, которая ссылается на avcodec, я получаю следующая ошибка: «Компилятор не выровнял переменные стека. Libavcodec имеет был неправильно скомпилирован », после чего произошел сбой в avcodec.dll.
  • avcodec.dll не был скомпилирован с msvc, поэтому я не могу видеть, что происходит внутри.
  • При запуске ffmpeg.exe и использовании того же avcodec.dll все работает хорошо.
  • ffmpeg.exe не был скомпилирован с msvc, он соответствовал gcc / mingw (так же, как avcodec.dll)

Спасибо

Dan

Ответы [ 4 ]

106 голосов
/ 23 марта 2009

Выравнивание переменных в памяти (краткая история).

В прошлом компьютеры имели 8 бит данных. Это означает, что каждый тактовый цикл может обрабатывать 8 бит информации. Что было хорошо тогда.

Затем появились 16-битные компьютеры. Из-за обратной совместимости и других проблем 8-битный байт был сохранен, и было введено 16-битное слово. Каждое слово было 2 байта. И каждый тактовый цикл может обрабатывать 16 бит информации. Но это поставило небольшую проблему.

Давайте посмотрим на карту памяти:

+----+
|0000| 
|0001|
+----+
|0002|
|0003|
+----+
|0004|
|0005|
+----+
| .. |

На каждом адресе имеется байт, доступ к которому можно получить индивидуально. Но слова могут быть получены только по четным адресам. Поэтому, если мы читаем слово в 0000, мы читаем байты в 0000 и 0001. Но если мы хотим прочитать слово в позиции 0001, нам нужно два доступа для чтения. Сначала 0000,0001, а затем 0002,0003, и мы оставляем только 0001,0002.

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

Например, если у нас есть структура с байтовым полем (B) и словом (W) (и очень наивный компилятор), мы получим следующее:

+----+
|0000| B
|0001| W
+----+
|0002| W
|0003|
+----+

Что не весело. Но при использовании выравнивания слов мы находим:

+----+
|0000| B
|0001| -
+----+
|0002| W
|0003| W
+----+

Здесь память жертвуется ради скорости доступа.

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

12 голосов
/ 23 марта 2009

Некоторые архитектуры ЦП требуют определенного выравнивания различных типов данных и выдают исключения, если вы не соблюдаете это правило. В стандартном режиме x86 не требует этого для основных типов данных, но может пострадать из-за снижения производительности (см. Www.agner.org для получения советов по оптимизации низкого уровня).

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

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

РЕДАКТИРОВАТЬ : что касается того, почему происходит исключение, подпрограмма в DLL, вероятно, хочет использовать инструкции SSE для некоторых данных временного стека, и завершается неудачно, потому что два разных компилятора не согласовывают соглашения о вызовах.

11 голосов
/ 23 марта 2009

IIRC, выравнивание стека - это когда переменные помещаются в стек, «выровненные» по определенному количеству байтов. Поэтому, если вы используете 16-разрядное выравнивание стека, каждая переменная в стеке будет начинаться с байта, кратного 2 байтам от текущего указателя стека в функции.

Это означает, что если вы используете переменную размером <2 байта, например, char (1 байт), между ней и следующей переменной будет 8 бит неиспользованного «заполнения». Это допускает определенную оптимизацию с допущениями, основанными на переменных местоположениях. </p>

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

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

2 голосов
/ 23 марта 2009

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

// Some compilers won't align this as it's on the stack...
int __declspec(align(32)) needsToBe32Aligned = 0;
// Change to
static int __declspec(align(32)) needsToBe32Aligned;
needsToBe32Aligned = 0;

Либо найдите переключатель компилятора, который выравнивает переменные в стеке. Очевидно, что использованный здесь синтаксис выравнивания "__declspec" может не совпадать с тем, что использует ваш компилятор.

...