что такое "выравнивание стека"?
Что такое выравнивание стека? Почему он используется? Может ли он управляться настройками компилятора?
детали этого вопроса взяты из проблемы, с которой сталкиваются при попытке использовать библиотеки ffmpeg с msvc, однако то, что меня действительно интересует, - это объяснение того, что такое "выравнивание стека".
Детали:
- когда runnig мой msvc выполнил программу, которая ссылается на avcodec я получаю следующая ошибка: "компилятор не выровнял переменные стека. Libavcodec имеет были miscompiled", после аварии в avcodec.файл DLL.
- avcodec.dll не была скомпилирована с msvc, поэтому я не могу видеть, что происходит внутри.
- при запуске ffmpeg.exe и используя тот же avcodec.dll все работает хорошо.
- через FFmpeg.exe не был скомпилирован с msvc, он был выполнен с gcc / mingw (так же, как avcodec.dll файлы)
спасибо,
Дэн
4 ответов
выравнивание переменных в памяти (Краткая история).
в прошлом компьютеры имели 8-битный databus. Это означает, что каждый такт может обрабатывать 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
+----+
здесь память жертвуется для скорости доступа.
вы можете себе представить, что при использовании double word (4 байта) или quad word (8 байт) это еще более важно. Вот почему с большинством современных компиляторов вы можете выбрать, какой расклад вы используете при компиляции программы.
некоторые архитектуры ЦП требуют определенного выравнивания различных типов данных и будут создавать исключения, если вы не соблюдаете это правило. В стандартном режиме x86 не требует этого для основных типов данных, но может понести штрафы за производительность (проверка www.agner.org для низкоуровневых советов по оптимизации).
на SSE набор инструкций (часто используемый для высокопроизводительного) аудио / видео procesing имеет строгие требования к выравниванию, и бросит исключения, если вы попытаетесь чтобы использовать его на несогласованных данных (если вы не используете, на некоторых процессорах, гораздо медленнее несогласованных версий).Ваш вопрос наверное что один компилятор ожидает абонента сохранить стек выровнен, в то время как другой ожидает callee для выравнивания стека при необходимости.
редактировать: Что касается того, почему происходит исключение, подпрограмма в DLL, вероятно, хочет использовать инструкции SSE для некоторых временных данных стека и терпит неудачу потому что два разных компилятора не согласны с соглашениями о вызове.
IIRC, выравнивание стека-это когда переменные помещаются в стек, "выровненный" по определенному количеству байтов. Поэтому, если вы используете 16-битное выравнивание стека, каждая переменная в стеке будет начинаться с байта, кратного 2 байтам от текущего указателя стека в функции.
Это означает, что если вы используете переменную
при вызове функций одним из способов передачи аргументов следующей функции является их размещение в стеке (в отличие от размещения их непосредственно в регистрах). Важно, используется ли здесь выравнивание, так как вызывающая функция помещает переменные в стек, чтобы считывать вызывающую функцию с помощью смещений. Если вызывающая функция выравнивает переменные, и вызываемая функция ожидает, что они будут не выровнены, тогда вызываемая функция не сможет их найти.
похоже, что скомпилированный код msvc не согласен с выравниванием переменных. Попробуйте выполнить компиляцию с отключенными оптимизациями.
насколько я знаю, компиляторы обычно не выравнивают переменные, которые находятся в стеке. Библиотека может зависеть от некоторого набора параметров компилятора, который не поддерживается компилятором. Нормальным решением является объявление переменных, которые должны быть выровнены как статические, но если вы собираетесь делать это в коде других людей, вы захотите убедиться, что они переменные, о которых идет речь, инициализируются позже в функции, а не в объявлении.
// 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", который я использовал здесь, может быть не тем, что использует ваш компилятор.