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

Я пытаюсь изучить x86. (архитектура IA-32) Сегодня я узнал о стеке. Кажется, я понял вот что:

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

int addSome(int arg1, int arg2);

int main(void){
  int answer = addSome(1,2);
}


int addSome(int arg1, int arg2){
  return arg1 + arg2;
}

Стек должен выглядеть так:

Стек, в котором указатель кадра показывает «поп-адрес»

Что произойдет с ФП, если есть вложенные функции? Ее нельзя переместить вверх, так как ее местоположение понадобится, если подфункция будет выдвинута.

Для визуализации; добавив эту функцию, которая будет вызываться внутри addSome():

int subSome(int arg3, int arg4);
int subSome(int arg3, int arg4){
  return arg3 - arg4;
}

Как здесь обрабатывается ФП? Откуда известен новый «популярный адрес» и местонахождение новых аргументов? Стек, где указатель кадра показывает «поп-адрес» первой функции, вторая неизвестна

Я предполагаю, что размер стека addSome будет сохранен где-то, чтобы получить местоположение нового «поп-адреса» относительно FP:

Стек, в котором указатель кадра имеет относительное отличие от нового «поп-адреса»

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

Что вы подразумеваете под «вложенными функциями»? Я спрашиваю, потому что некоторые языки, например Паскаль, поддерживают статически вложенные функции, а другие, например C, нет. Статически вложенные функции позволяют внутренней/вложенной функции получать доступ к локальным переменным внешней/вложенной функции, во многом подобно лямбде и ее замыканию (как и замыкание, внутренняя/вложенная функция не может быть вызвана извне, ее необходимо вызывать из внешней /вложенная функция). Однако все языки позволяют функциям вызывать другие функции, поэтому это просто стек вызовов или динамическая цепочка вызовов.

Erik Eidt 02.09.2024 18:37
Вложенная функция — В компьютерном программировании вложенная функция (или вложенная процедура или подпрограмма) — это именованная функция, которая определена внутри другого включающего блока и имеет лексическую область действия внутри включающего блока — то есть ее можно вызвать только по имени внутри тело включающего блока и может использовать идентификаторы, объявленные во внешних блоках, включая внешние функции. Включающий блок обычно, но не всегда, представляет собой другую функцию. Итак, вы показываете просто цепочку вызовов, а не вложенные функции.
Erik Eidt 02.09.2024 19:32

Да, судя по вашим комментариям, «вложенные функции» — неправильный термин. Спасибо, я неправильно понял этот термин и использовал его неправильно. => чему-то научился

Sukram 22 03.09.2024 07:49
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
3
53
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Прежде всего, в x86 регистр, который мы используем для адресации значений в кадре стека, называется «базовым указателем» или ebp. Я не знаю, почему вы это называете fp.

Во-вторых, нам не нужно обсуждать subSome(), потому что вторая, вложенная функция у нас уже есть, так как у нас есть две функции: main() и addSome().

Стандартный пролог и эпилог функции в x86 выглядит так:

push ebp       ; save caller's ebp
mov ebp, esp   ; make ebp point to the frame of this function
...            ; main function body goes here
pop ebp        ; restore caller's ebp
ret            ; return to caller

Обычная, реальная, физическая стопка (скажем, стопка карт) растет снизу вверх. Когда вы добавляете в стопку еще одну карту, она оказывается наверху стопки. Не так в x86; в x86 стек растет сверху вниз. Инструкция push <32-bit-operand> уменьшает регистр esp на 4, а затем сохраняет 32-битный операнд по адресу памяти, указанному новым значением регистра esp. Инструкция pop <32-bit-operand> делает обратное.

Более того, возвращаемое значение обычно не сохраняется в стеке. Подробности зависят от действующего ABI (двоичного интерфейса приложения), но обычно они возвращаются в регистре eax. Возможно, вы имели в виду адрес возврата, который фактически помещается в стек инструкцией call и извлекается из стека инструкцией ret.

Итак, внутри addSome() ваш стек будет выглядеть так:

top-of-stack        `argc` parameter to `main()` pushed by the standard library
top-of-stack - 4    `argv` parameter to `main()`  pushed by the standard library
top-of-stack - 8    return address of standard library code that invoked `main()`
top-of-stack - 12   ebp that was saved by `main()`
top-of-stack - 16   arg1 parameter to `addSome()` (value: 1)
top-of-stack - 20   arg2 parameter to `addSome()` (value: 2)
top-of-stack - 24   return address back to `main()`
top-of-stack - 28   ebp that was saved by `addSome()` <-- esp points here

(обратите внимание, что top-of-stack обычно не будет настоящей вершиной стека; над всем остальным будет еще кое-что, выдвинутое кодом стандартной библиотеки, который вызвал ваш main(). Но показанная часть - это та часть, которая нас волнует.)

esp указывает на самые последние отправленные данные, поэтому в вашей таблице top of stack - 28 будет равно esp, а не минус 32. esp будет уменьшено до минус 32, если нужно будет отправить еще одно слово.
ecm 02.09.2024 22:29

@Майк Накис Спасибо! ebp тоже помещается в стек, это имеет смысл. Я могу это понять именно так. Примеры очень помогают. Спасибо, что указали и на другие мои ошибки.

Sukram 22 03.09.2024 07:55

@ecm ты прав, я исправил, спасибо

Mike Nakis 03.09.2024 08:00

Другие вопросы по теме