Современные операционные системы (Windows, Linux) работают с тем, что я называю «моделью большого стека». И эта модель иногда неверна и мотивирует потребность в языках без стеков.
«Модель большого стека» предполагает, что скомпилированная программа будет выделять «кадры стека» для вызовов функций в непрерывной области памяти, используя машинные инструкции для очень быстрой настройки регистров, содержащих указатель стека (и необязательный указатель стека фреймов). Это приводит к быстрому вызову / возврату функции за счет наличия большой непрерывной области для стека. Поскольку 99,99% всех программ, работающих под управлением этих современных ОС, хорошо работают с моделью большого стека, компиляторы, загрузчики и даже ОС «знают» об этой области стека.
Одна общая проблема, с которой сталкиваются все такие приложения: "насколько большим должен быть мой стек?" . Поскольку память очень дешевая, в основном происходит то, что для стека выделяется большой кусок (по умолчанию MS равен 1 МБ), и типичная структура вызова приложения никогда не приближается к его использованию. Но если приложение использует все это, оно умирает с недопустимой ссылкой на память («Простите, Дейв, я не могу этого сделать») из-за того, что оно достигло конца своего стека.
Большинство так называемых языков без стеков в действительности не являются без стеков. Они просто не используют непрерывный стек, предоставляемый этими системами. Вместо этого они выделяют кадр стека из кучи при каждом вызове функции. Стоимость вызова функции несколько возрастает; если функции обычно сложные или язык интерпретирующий, эти дополнительные расходы незначительны. (Можно также определить группы доступности базы данных в графе вызовов программы и выделить сегмент кучи для охвата всей группы доступности базы данных; таким образом вы получаете как распределение кучи, так и скорость классических вызовов функций большого стека для всех вызовов внутри группы вызова DAG).
Существует несколько причин для использования выделения кучи для стековых фреймов:
1) Если программа выполняет глубокую рекурсию в зависимости от конкретной проблемы, которую она решает,
очень трудно заранее выделить область «большого стека», потому что необходимый размер неизвестен. Можно неуклюже организовать вызовы функций, чтобы проверить, достаточно ли осталось стека, а если нет, перераспределить больший кусок, скопировать старый стек и перенастроить все указатели в стек; это так неловко, что я не знаю ни одной реализации.
Выделение кадров стека означает, что приложение никогда не должно извиняться, пока не будет
Буквально не осталось выделенной памяти.
2) Программа разветвляется на подзадачи. Каждая подзадача требует своего собственного стека и поэтому не может использовать один предоставленный «большой стек». Итак, нужно выделить стеки для каждой подзадачи. Если у вас есть тысячи возможных подзадач, теперь вам могут понадобиться тысячи «больших стеков», и потребность в памяти внезапно становится нелепой. Выделение кадров стека решает эту проблему. Часто «стеки» подзадач относятся к родительским задачам для реализации лексической области видимости; в качестве ответвления подзадач создается дерево «подстаков», которое называется «стек кактусов».
3) У вашего языка есть продолжения. Это требует, чтобы данные в лексической области видимости текущей функции каким-то образом были сохранены для последующего повторного использования. Это может быть реализовано путем копирования кадров родительского стека, подъема вверх по стеку кактусов и продолжения.
Язык программирования PARLANSE , который я реализовал, выполняет 1) и 2). Я работаю над 3).