Понимание стека кадра вызова функции в C / C ++?

19

Я пытаюсь понять, как строятся стековые фреймы и какие переменные (параметры) помещаются в стек в каком порядке? Некоторые результаты поиска показали, что компилятор C / C ++ принимает решение на основе операций, выполняемых внутри функции. Например, если функция должна была просто увеличить переданное значение int на 1 (аналогично оператору ++) и вернуть его, она поместит все параметры функции и локальные переменные в регистры.

Мне интересно, какие регистры используются для возвращаемых или передаваемых по значению параметров. Как возвращаются ссылки? Как компилятор выбирает между eax, ebx, ecx и edx?

Что мне нужно знать, чтобы понять, как регистры, ссылки на стек и кучи используются, создаются и уничтожаются во время вызовов функций?

Gana
источник
это довольно трудно читать (стена текста). Не могли бы вы отредактировать ваш пост в лучшую форму?
комнат
1
Этот вопрос мне кажется довольно широким. Кроме того, разве это не будет зависеть от платформы?
Казарк
Вопрос был также задан на SO: stackoverflow.com/questions/16088040/…
Уэйн Конрад
Смотрите также мой ответ на SO
Василий Старынкевич

Ответы:

11

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

Таким образом, вызов функции B из функции A в типичной «универсальной» системе может включать следующие шаги:

  • функция А:
    • вставить пробел для возвращаемого значения
    • параметры нажатия
    • нажмите обратный адрес
  • перейти к функции B
  • функция B:
    • нажмите адрес предыдущего стека кадра
    • выдавать значения регистров, которые использует эта функция (чтобы их можно было восстановить)
    • пробел для локальных переменных
    • сделать необходимые вычисления
    • восстановить регистры
    • восстановить предыдущий кадр стека
    • сохранить результат функции
    • перейти на обратный адрес
  • функция А:
    • поп параметры
    • поп возвращаемое значение

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

Калеб
источник
Что именно здесь означает «толчок»? Я понятия не имею, что с этим делать.
Томаш Зато - Восстановить Монику
2
@ TomášZato pushи popявляются двумя основными операциями в стеке. Стек - это структура «первым пришел - первым вышел», как стопка книг. Когда вы push, вы помещаете новый объект поверх стека; когда вы popберете объект с вершины стека. Вы не можете вставлять или удалять объекты в середине, вы можете работать только на вершине стека. Вы можете прочитать больше о стеках в целом и о программном стеке в частности в Википедии.
Калеб
11

Это зависит от используемого соглашения о вызовах. Тот, кто определяет соглашение о вызовах, может принять это решение, как ему угодно.

В наиболее распространенном соглашении о вызовах в x86 регистры не используются для передачи параметров; параметры помещаются в стек, начиная с самого правого параметра. Возвращаемое значение помещается в eax и может использовать edx, если ему нужно дополнительное пространство. Ссылки и указатели возвращаются в виде адреса в eax.

Дирк Холсоппл
источник
5

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

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

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

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

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

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

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)

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

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

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

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

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

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

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

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

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

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

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

Это очень краткое описание, и если вы хотите узнать больше о стеке и двойной рекурсии, прочитайте два поста этого блога:

Подробнее о стеке шаг за шагом

Подробнее о двойной рекурсии шаг за шагом со стеком

user5904928
источник
3

То, что вы ищете, называется Application Binary Interface - ABI.

Существует спецификация для каждого компилятора, в которой прописан ABI.

Каждая платформа обычно указывает и ABI для поддержки взаимодействия между компиляторами. Например, x86 соглашение о вызовах прописываются типичными соглашения о вызовах для x86 и x86-64. Однако я бы ожидал более официальный документ, чем википедия.

Билл Дверь
источник