Объясните концепцию стекового фрейма в двух словах

200

Кажется, я понял идею стека вызовов в дизайне языка программирования. Но я не могу найти (вероятно, я просто недостаточно тщательно ищу) какое-либо достойное объяснение того, что такое фрейм стека .

Поэтому я хотел бы попросить кого-нибудь объяснить мне это в нескольких словах.

ikostia
источник

Ответы:

195

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

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

РЕДАКТИРОВАТЬ:

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

Когда мы говорим о стеке вызовов процессора, мы говорим о работе с адресами и значениями на уровне байтов / слов в сборке или машинном коде. Существуют «стеки вызовов», когда речь идет о языках более высокого уровня, но это инструмент отладки / выполнения, управляемый средой выполнения, так что вы можете регистрировать, что пошло не так с вашей программой (на высоком уровне). На этом уровне такие вещи, как номера строк, имена методов и классов, часто известны. К тому времени, когда процессор получает код, он совершенно не имеет понятия об этих вещах.

Тони Р
источник
6
«Процессор знает, сколько байтов находится в каждом кадре, и соответственно перемещает указатель стека, когда кадры выталкиваются и выталкиваются из стека». - Я сомневаюсь, что процессор знает что-нибудь о стеке, потому что МЫ манипулируем им посредством подподчинения (выделения), нажатия и выталкивания. Итак, вот соглашения о вызовах, которые объясняют, как мы должны использовать стек.
Виктор Полевой
78

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

Позвольте мне объяснить, как работает стек:

Сначала вы должны знать, как функции представлены в стеке:

Куча хранит динамически распределяемые значения.
Стек хранит значения автоматического распределения и удаления.

введите описание изображения здесь

Давайте разберемся с примером:

def hello(x):
    if x==1:
        return "op"
    else:
        u=1
        e=12
        s=hello(x-1)
        e+=1
        print(s)
        print(x)
        u+=1
    return e

hello(4)

Теперь разберитесь в частях этой программы:

введите описание изображения здесь

Теперь давайте посмотрим, что такое стек и что такое части стека:

введите описание изображения здесь

Выделение стека:

Помните одну вещь: если условие возврата какой-либо функции удовлетворяется, независимо от того, загружены ли локальные переменные или нет, она немедленно вернется из стека со своим фреймом стека. Это означает, что всякий раз, когда любая рекурсивная функция получает базовое условие, и мы помещаем возврат после базового условия, базовое условие не будет ждать загрузки локальных переменных, которые находятся в части «else» программы. Он немедленно вернет текущий кадр из стека, после чего следующий кадр теперь находится в записи активации.

Смотрите это на практике:

введите описание изображения здесь

Распределение блока:

Поэтому теперь, когда функция встречает оператор return, она удаляет текущий кадр из стека.

При возврате из стека значения будут возвращаться в обратном порядке, в котором они были размещены в стеке.

введите описание изображения здесь

Аадитя Ура
источник
3
стек растет вниз, а куча растет вверх, вы обращаете их вспять на диаграмме. ПРАВИЛЬНАЯ ДИАГРАММА ЗДЕСЬ
Рафаэль
@Rafael извините за путаницу, я говорил о направлении роста, я не говорил о направлении роста стека. Есть различие между направлением роста и направлением роста стека. Смотрите здесь stackoverflow.com/questions/1677415/…
Адитья Ура
2
Рафаэль прав. Также первая картина не так. Замените его на что-то другое (поищите в гугл-картинках "стек кучи").
Никос
Так что, если я правильно понимаю, на вашей третьей диаграмме есть 3 стековых фрейма, потому что hello()он рекурсивно вызвал, hello()который затем (снова) рекурсивно вызвал hello(), а глобальный фрейм - это оригинальная функция, которая вызвала первый hello()?
Энди Дж
1
Куда ведут нас ссылки? В качестве серьезной проблемы безопасности эти ссылки должны быть удалены как можно скорее.
Шиваншу
45

Быстрое завершение. Может быть, у кого-то есть лучшее объяснение.

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

Чтобы использовать кадр стека, поток сохраняет два указателя, один из которых называется указателем стека (SP), а другой - указателем кадра (FP). SP всегда указывает на «вершину» стека, а FP всегда указывает на «вершину» кадра. Кроме того, поток также поддерживает программный счетчик (ПК), который указывает на следующую команду, которая будет выполнена.

В стеке хранятся следующие данные: локальные переменные и временные значения, фактические параметры текущей инструкции (процедура, функция и т. Д.)

Существуют разные соглашения о вызовах, касающиеся очистки стека.

ervinbosenbacher
источник
7
Не забывайте, что адрес возврата подпрограммы помещается в стек.
Тони Р
4
Frame Pointer также является базовым указателем в терминах x86
peterchaula
1
Я хотел бы подчеркнуть, что указатель кадра указывает на начало кадра стека для текущей активной процедуры воплощения.
Сервер Халилова
13

«Стек вызовов состоит из кадров стека ...» -  Википедия

Фрейм стека - это то, что вы кладете в стек. Это структуры данных, которые содержат информацию о подпрограммах для вызова.

Валид Хан
источник
Извините, я понятия не имею, как я пропустил это в вики. Спасибо. Правильно ли я понимаю, что в динамических языках размер фрейма не является постоянным значением, поскольку локальные функции не точно известны?
ikostia
Размер и характер рамы сильно зависят от архитектуры машины. Фактически, сама парадигма стека вызовов зависит от архитектуры. Насколько я знаю, это всегда переменная, потому что разные вызовы функций будут иметь разное количество данных аргумента.
Тони Р
Обратите внимание, что размер кадра стека должен быть известен процессору, когда им манипулируют. Когда это происходит, размер данных уже определен. Динамические языки компилируются в машинный код точно так же, как статические языки, но часто выполняются точно в срок, чтобы компилятор мог поддерживать динамизм, а процессор мог работать с «известными» размерами кадров. Не путайте языки более высокого уровня с машинным кодом / сборкой, где это происходит на самом деле.
Тони Р
Ну, но динамические языки также имеют свои стеки вызовов, не так ли? Я имею в виду, если, скажем, Python хочет выполнить какую-то процедуру, данные об этой процедуре хранятся в структуре некоторого интерпретатора Python, я прав? Так что я имею в виду, что стек вызовов присутствует не только на низком уровне.
ikostia
Прочитав немного этой статьи в Википедии, я исправился (немного). Размер кадра стека может оставаться неизвестным во время компиляции . Но к тому времени, когда процессор работает с указателями стека + фрейма, он должен знать, каковы размеры. Размер может быть переменным, но процессор знает размер, это то, что я пытался сказать.
Тони Р
5

У программистов могут возникнуть вопросы о фреймах стека не в широком смысле (что это единичная сущность в стеке, которая обслуживает только один вызов функции и сохраняет адрес возврата, аргументы и локальные переменные), но в узком смысле - когда термин stack framesупоминается в контекст параметров компилятора.

Независимо от того, имел в виду автор вопроса или нет, но концепция стекового фрейма с точки зрения параметров компилятора - очень важная проблема, которая здесь не рассматривается в других ответах.

Например, компилятор Microsoft Visual Studio 2015 C / C ++ имеет следующий параметр, связанный с stack frames:

  • / Oy (Frame-Pointer Omission)

GCC имеют следующее:

  • -fomit-frame-pointer (Не хранить указатель кадра в регистре для функций, которые в нем не нуждаются. Это позволяет избежать инструкций по сохранению, настройке и восстановлению указателей кадра; также делает дополнительный регистр доступным во многих функциях )

Компилятор Intel C ++ имеет следующее:

  • -fomit-frame-pointer (Определяет, используется ли EBP как регистр общего назначения при оптимизации)

который имеет следующий псевдоним:

  • / Oy

Delphi имеет следующую опцию командной строки:

  • - $ W + (Создание кадров стека)

В этом конкретном смысле, с точки зрения компилятора, кадр стека - это просто код входа и выхода для подпрограммы , который вставляет привязку к стеку - который также может использоваться для отладки и обработки исключений. Инструменты отладки могут сканировать данные стека и использовать эти привязки для обратного отслеживания, находясь call sitesв стеке, то есть для отображения имен функций в том порядке, в котором они были вызваны иерархически. Для архитектуры Intel это push ebp; mov ebp, espлибо enterдля входа, mov esp, ebp; pop ebpлибо leaveдля выхода.

Вот почему для программиста очень важно понять, что такое стековый фрейм, когда дело доходит до опций компилятора - потому что компилятор может контролировать, генерировать этот код или нет.

В некоторых случаях кадр стека (код входа и выхода для процедуры) может быть пропущен компилятором, и переменные будут напрямую доступны через указатель стека (SP / ESP / RSP), а не через удобный базовый указатель (BP / ESP / РСП). Условия пропуска стекового фрейма, например:

  • функция является конечной (то есть конечной сущностью, которая не вызывает другие функции);
  • не существует try / finally или try / кроме или аналогичных конструкций, т.е. не используются исключения;
  • никакие процедуры не вызываются с исходящими параметрами в стеке;
  • функция не имеет параметров;
  • функция не имеет встроенного кода сборки;
  • и т.д...

Пропуск кадров стека (код входа и выхода для подпрограммы) может сделать код меньше и быстрее, но это также может отрицательно повлиять на способность отладчиков отслеживать данные в стеке и отображать их для программиста. Это параметры компилятора, которые определяют, при каких условиях функция должна иметь код входа и выхода, например: (a) всегда, (b) никогда, (c) при необходимости (с указанием условий).

Максим Масютин
источник
-1

Кадр стека - это упакованная информация, связанная с вызовом функции. Эта информация обычно включает аргументы, передаваемые в функцию, локальные переменные и куда возвращаться после завершения. Запись активации - это другое имя стекового фрейма. Компоновка кадра стека определяется в ABI производителем, и каждый компилятор, поддерживающий ISA, должен соответствовать этому стандарту, однако схема компоновки может зависеть от компилятора. Обычно размер кадра стека не ограничен, но существует концепция, называемая «красная / защищенная зона», позволяющая системным вызовам ... и т. Д. Выполняться без вмешательства в кадр стека.

Всегда есть SP, но на некоторых ABI (например, ARM и PowerPC) FP не обязателен. Аргументы, которые нужно было поместить в стек, можно сместить только с помощью SP. Будет ли сгенерирован кадр стека для вызова функции или нет, зависит от типа и количества аргументов, локальных переменных и от того, как вообще доступны локальные переменные. В большинстве ISA, во-первых, используются регистры, и если имеется больше аргументов, чем регистров, выделенных для передачи аргументов, они помещаются в стек (например, x86 ABI имеет 6 регистров для передачи целочисленных аргументов). Следовательно, иногда некоторые функции не нуждаются в размещении кадра стека в стеке, просто адрес возврата помещается в стек.

пьяный чайник
источник