Невозможно реализовать семантику вызова функции без использования какого-либо стека. Играть можно только в игры в слова (например, использовать для него другое имя, например, «FILO return buffer»).
Можно использовать что-то, что не реализует семантику вызова функции (например, стиль передачи продолжения, актеры), а затем построить семантику вызова функции поверх этого; но это означает добавление некоторой структуры данных для отслеживания того, где передается управление, когда функция возвращается, и эта структура данных будет представлять собой тип стека (или стека с другим именем / описанием).
Представьте, что у вас есть много функций, которые могут вызывать друг друга. Во время выполнения каждая функция должна знать, куда возвращаться при выходе из функции. Если first
звонки, second
то у вас есть:
second returns to somewhere in first
Тогда, если second
звонки у third
вас есть:
third returns to somewhere in second
second returns to somewhere in first
Тогда, если third
звонки у fourth
вас есть:
fourth returns to somewhere in third
third returns to somewhere in second
second returns to somewhere in first
Поскольку каждая функция вызывается, больше информации "где вернуть" должно храниться где-то.
Если функция возвращается, то используется информация «куда возвращать», и она больше не нужна. Например, если fourth
возвращается обратно куда- third
то, то количество информации «где вернуться» станет:
third returns to somewhere in second
second returns to somewhere in first
В принципе; «семантика вызова функции» подразумевает, что:
- у вас должна быть информация "где вернуть"
- количество информации увеличивается при вызове функций и уменьшается при возврате функций
- первая часть сохраненной информации «куда возвращать» будет последней частью отброшенной информации «куда возвращать»
Это описывает буфер FILO / LIFO или стек.
Если вы попытаетесь использовать тип дерева, то у каждого узла в дереве никогда не будет более одного дочернего элемента. Примечание. Узел с несколькими дочерними элементами может возникнуть только в том случае, если функция вызывает 2 или более функций одновременно , что требует некоторого параллелизма (например, threads, fork () и т. Д.), И это не будет «семантикой вызова функции». Если каждый узел в дереве никогда не будет иметь более одного дочернего элемента; тогда это «дерево» будет использоваться только в качестве буфера FILO / LIFO или стека; и поскольку он используется только в качестве буфера FILO / LIFO или стека, можно утверждать, что «дерево» является стеком (и единственное отличие заключается в играх в слова и / или деталях реализации).
То же самое относится к любой другой структуре данных, которая могла бы использоваться для реализации «семантики вызова функции» - она будет использоваться в качестве стека (и единственное отличие - это игры в слова и / или детали реализации); если это не нарушает "семантику вызова функции". Примечание: я бы привел примеры для других структур данных, если бы мог, но я не могу представить себе какую-либо другую структуру, которая была бы немного правдоподобной.
Конечно, как реализован стек - это деталь реализации. Это может быть область памяти (где вы отслеживаете «текущую вершину стека»), это может быть какой-то связанный список (где вы отслеживаете «текущую запись в списке»), или она может быть реализована в некоторых другой путь. Также не имеет значения, есть ли у оборудования встроенная поддержка или нет.
Примечание. Если в любой момент может быть активен только один вызов какой-либо процедуры; тогда вы можете статически выделять место для информации «где вернуться». Это по-прежнему стек (например, связанный список статически распределенных записей, используемых способом FILO / LIFO).
Также обратите внимание, что есть некоторые вещи, которые не следуют «семантике вызова функции». Эти вещи включают «потенциально очень различную семантику» (например, передача продолжения, модель актера); а также включает общие расширения «семантики вызовов функций», такие как параллелизм (потоки, волокна и т. д.), setjmp
/ longjmp
, обработка исключений и т. д.