Стек вызовов начинается снизу или сверху?

11

Стек - это то, что накапливается снизу вверх.

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

Если вышеприведенное верно, почему люди обращаются к управлению, перемещающемуся вверх по стеку вызовов? Конечно, управление перемещается вниз по стеку вызовов, пока не достигнет дна.

CJ7
источник
Когда вызывается функция, элемент добавляется в верхнюю часть стека, и управление передается этой функции. Следовательно, элемент управления перемещается от базового элемента в стеке к верхнему элементу - вверх.
Treecoder
1
@greengit: выражение «вверх по стеку вызовов» используется с исключениями, когда управление фактически перемещается в противоположном направлении.
Майкл Боргвардт
@MichaelBorgwardt: Вы правы.
Treecoder
1
@MichaelBorgwardt: я видел выражение «ошибки поднимают стек вызовов». Конечно, это неправильно.
CJ7

Ответы:

9

Есть две возможные причины такого использования:

  • В контексте исключений элемент управления переходит к вызывающей функции / методу, и эта иерархия вызовов обычно визуализируется с основным методом сверху и вызовами методов, образующими иерархию вниз с уменьшающимся уровнем абстракций. В этой иерархии исключение перемещается вверх.

  • Фактический программный стек в обычном приложении x86 инвертируется, то есть растет вниз. Инструкции машинного кода PUSH / PUSHW / PUSHD уменьшают указатель стека. Другие архитектуры могут использовать эту модель.

Майкл Боргвардт
источник
Разве нисходящая идея не противоречит повседневной концепции «стопки», представляющей собой кучу предметов, начинающихся снизу?
CJ7
@CraigJ: таков тот факт, что биты каждого байта содержимого стека будут физически храниться в отдельных чипах. Какая разница?
Майкл Боргвардт
1

Все зависит от определения слов; что именно вы подразумеваете под словами «верх» и «низ» в этом контексте, а также под реализацией операционной системы или компьютерной архитектуры.

Я помню следующее давно, когда я программировал на Commodore 64. Память между адресами $ 0800 (2048) и $ 9FFF (40959) была зарезервирована для программ BASIC. Код вашей BASIC-программы хранился, начиная с нижнего адреса ($ 800, оттуда вверх). Стек для хранения переменных и адресов возврата подпрограмм начинался с вершины ($ 9FFF) этого диапазона и увеличивался в сторону более низких адресов. Таким образом, в этом контексте было логично видеть, что стек растёт вниз, и когда вы возвращаетесь из подпрограммы, кадр стека подпрограммы отбрасывается путем увеличения указателя стека, так что вы можете сказать, что «двигались вверх по стеку», когда возвращаясь из подпрограммы.

Я не знаю, как это работает на современных версиях, например, процессоров Windows или Intel x86. Возможно, стек работает наоборот, т. Е. Растет с более низких адресов на более высокие. Если бы это было так, то вы, вероятно, использовали бы слова «верх», «низ» и «вверх», «вниз» в точности наоборот.

Jesper
источник
0

Для вызова такой функции, как foo (6, x + 1) ...

  1. Оцените фактические выражения параметров, такие как x + 1, в контексте вызывающего.
  2. Выделите память для локальных объектов foo (), поместив подходящий «локальный блок» памяти в «стек вызовов» времени выполнения, предназначенный для этой цели. Для параметров, но не для локальных переменных, сохраните значения из шага (1) в соответствующий слот в локальном блоке foo ().
  3. Сохраните текущий адрес выполнения вызывающей стороны (его «обратный адрес») и переключите выполнение на foo ().
  4. foo () выполняется с локальным блоком, удобно доступным в конце стека вызовов.
  5. Когда foo () завершается, он выходит, выталкивая свои локальные объекты из стека и «возвращается» к вызывающей стороне, используя ранее сохраненный адрес возврата. Теперь локальные объекты вызывающего абонента находятся в конце стека, и он может возобновить выполнение.

Ссылка:

http://cslibrary.stanford.edu/102/PointersAndMemory.pdf (стр. 15)

CodeART
источник
Обратите внимание, что это очень специфично для соглашения о вызовах. Существуют соглашения о вызовах, которые позволяют вызывающему абоненту
0

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

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

Это просто модели в вашей голове, и они абсолютно произвольны. Вы можете осмыслить это как вещь бок о бок, если хотите, но у вас могут быть проблемы с общением с людьми. Лично я считаю, что если A вызывает B, а B вызывает C, то C - это дно стека (инвертированная гравитационная реальность), и если в C возникает исключение, вы хотите выбросить это исключение «до» A. Я думаю, что это может быть более распространенное использование языка, потому что чувствуется, что C глубоко внутри, а A - верх. Первая функция более интуитивно понятна, и функции становятся глубже при каждом вызове.

Кен Фальк
источник