Почему стеки обычно растут вниз?
Я знаю, что в архитектурах, с которыми я лично знаком (x86, 6502 и т. Д.), Стек обычно растет вниз (т.е. каждый элемент, помещенный в стек, приводит к уменьшению SP, а не к увеличению).
Мне интересно об историческом обосновании этого. Я знаю, что в унифицированном адресном пространстве удобно запускать стек на противоположном конце сегмента данных (скажем), поэтому существует проблема, только если две стороны сталкиваются в середине. Но почему стек традиционно получает верхнюю часть? Особенно учитывая, как это противоположно "концептуальной" модели?
(И обратите внимание, что в архитектуре 6502, стек также растет вниз, даже если он ограничен одной 256-байтовой страницей, и этот выбор направления кажется произвольным.)
11 ответов
Что касается исторического обоснования, я не могу сказать наверняка (потому что я не проектировал их). Мои мысли по этому поводу состоят в том, что у ранних процессоров исходный счетчик программ был установлен на 0, и было естественным желание запустить стек на другом конце и расти вниз, поскольку их код естественным образом растет вверх.
Кроме того, обратите внимание, что эта установка счетчика программы на 0 при сбросе не подходит для всех ранних процессоров. Например, Motorola 6809 будет получать счетчик программ с адресов
0xfffe/f
так что вы можете начать работать в произвольном месте, в зависимости от того, что было предоставлено по этому адресу (обычно, но не ограничиваясь этим, ПЗУ).
Одна из первых вещей, которые делали бы некоторые исторические системы, состояла бы в том, чтобы сканировать память сверху, пока она не нашла место, которое считывало бы то же записанное значение, так, чтобы оно знало фактическое установленное ОЗУ (например, z80 с адресным пространством 64 КБ). не обязательно иметь 64КБ или ОЗУ, на самом деле 64К был бы огромным в мои первые годы). Как только он найдет фактический верхний адрес, он соответствующим образом установит указатель стека и сможет начать вызывать подпрограммы. Это сканирование, как правило, выполняется процессором, выполняющим код в ПЗУ, как часть запуска.
Что касается роста стеков, не все они растут вниз, подробности смотрите в этом ответе.
Одно хорошее объяснение, которое я слышал, состояло в том, что некоторые машины в прошлом могли иметь только неподписанные смещения, поэтому вы бы хотели, чтобы стек увеличивался вниз, чтобы вы могли поразить своих местных жителей, не теряя лишних инструкций для имитации отрицательного смещения.
Стэнли Мазор (архитектор 4004 и 8080) объясняет, как направление роста стека было выбрано для 8080 (и, в конечном итоге, для 8086) в разделе "Микропроцессоры Intel: от 8008 до 8086":
Указатель стека был выбран для запуска "вниз" (с продвижением стека к меньшему объему памяти), чтобы упростить индексирование в стек из пользовательской программы (положительное индексирование) и упростить отображение содержимого стека с передней панели.
Одной из возможных причин может быть то, что это упрощает выравнивание. Если вы поместите в стек локальную переменную, которая должна быть размещена на 4-байтовой границе, вы можете просто вычесть размер объекта из указателя стека, а затем обнулить два младших бита, чтобы получить правильно выровненный адрес. Если стек растет вверх, обеспечение выравнивания становится немного сложнее.
Потому что тогда a использует тот же режим адресации, который обычно используется для сканирования строк и массивов.
Инструкция, которая извлекает значение из стека, должна делать две вещи: считать значение из памяти и корректировать указатель стека. Для этой операции существует четыре возможных варианта конструкции:
Сначала предварительно увеличьте указатель стека, а затем прочитайте значение. Это означает, что стек будет расти «вниз» (к более низким адресам памяти).
Сначала выполните декремент указателя стека, а затем прочитайте значение. Это означает, что стек будет расти «вверх» (к более высоким адресам памяти).
Сначала прочитайте значение, а затем постинкрементируйте указатель стека. Это означает, что стек будет расти вниз.
Сначала прочитайте значение, а затем постдекрементируйте указатель стека. Это означает, что стек будет расти вверх.
Во многих компьютерных языках (особенно C) строки и массивы передаются функциям как указатели на их первый элемент. Очень распространенной операцией является чтение элементов строки или массива по порядку, начиная с первого элемента. Для такой операции нужен только описанный выше режим постинкрементной адресации.
Кроме того, чтение элементов строки или массива более распространено, чем запись элементов. Действительно, существует множество стандартных библиотечных функций, которые вообще не выполняют запись (например,
strlen()
,
strchr()
,
strcmp()
)!
Следовательно, если у вас есть ограниченное количество режимов адресации в вашем наборе инструкций, наиболее полезным режимом адресации будет чтение с постинкрементом . Это приводит не только к наиболее полезным операциям со строками и массивами, но и к
POP
инструкция, увеличивающая стек вниз .
Тогда вторым по полезности режимом адресации будет запись после декремента , которую можно использовать для сопоставления
PUSH
инструкция.
Действительно, у PDP-11 были режимы постинкрементной и преддекрементной адресации, которые создавали стек, растущий вниз. Даже у VAX не было преинкремента или постдекремента.
IIRC стек растет вниз, потому что куча растет вверх. Это могло быть наоборот.
Я считаю, что это чисто дизайнерское решение. Не все из них растут вниз - посмотрите эту ветку SO для хорошего обсуждения направления роста стека на разных архитектурах.
Просто 2с больше:
Помимо всех упомянутых исторических обоснований, я совершенно уверен, что нет причин, которые бы действовали в современных процессорах. Все процессоры могут иметь подписанные смещения, и максимизация расстояния кучи / стека довольно спорна с тех пор, как мы начали работать с несколькими потоками.
Я лично считаю это недостатком дизайна безопасности. Если бы, скажем, разработчики архитектуры x64 изменили бы направление роста стека, переполнения стекового буфера были бы устранены - что является большой проблемой.
Я считаю, что соглашение началось с IBM 704 и его печально известного "регистра декремента". Современная речь будет называть это смещенным полем инструкции, но дело в том, что они пошли вниз, а не вверх.
Я не уверен, но я занимался программированием для VAX/VMS в те дни. Кажется, я помню, как одна часть памяти (куча??) росла, а стек снижался. Когда они встретились, у тебя не было памяти.
Одним из преимуществ нисходящего роста стека в минимальной встроенной системе является то, что один фрагмент ОЗУ может быть избыточно отображен как на страницу O, так и на страницу 1, что позволяет назначать нулевые переменные страницы, начиная с 0x000, а стек растет вниз от 0x1FF, максимально увеличивая количество, которое он должен был бы вырасти перед перезаписью переменных.
Одна из первоначальных целей дизайна 6502 заключалась в том, чтобы его можно было объединить, например, с 6530, в результате чего получилась двухчиповая микроконтроллерная система с 1 КБ ПЗУ программ, таймером, вводом-выводом и 64 байтами общего ОЗУ. между переменными стека и нулевой страницы. Для сравнения, минимальная встраиваемая система того времени на базе 8080 или 6800 состояла бы из четырех или пяти микросхем.