Путаница в стеке вызовов функций
Согласно вики:
вызывающая сторона помещает адрес возврата в стек, а вызываемая подпрограмма, когда она заканчивается, извлекает адрес возврата из стека вызовов и передает управление этому адресу.
Картинка из вики:
Я не совсем понимаю это. Скажем, у меня есть программа на C следующим образом:
#include <stdio.h>
int foo(int x)
{
return x+1;
}
void spam()
{
int a = 1; //local variable
int b = foo(a); //subroutine called
int c = b; //local variable
}
int main()
{
spam();
return 0;
}
И я думаю, что стек вызовов должен выглядеть примерно так:
<None> means none local variables or params
_| parameters for foo() <int x> |_
top | local of spam() <int c> |
^ | return address of foo() |<---foo() called, when finishes, return here?
| | local of spam() <int b> |
bot | local of spam() <int a> |
_| parameters for spam() <None> |_
| locals of main() <None> |
| return address of spam() |<---spam() called, when finishes, return here?
| parameters for main() <None> |
Вопрос:
Согласно словам, цитируемым из Вики,
вызываемая подпрограмма, когда она заканчивается, извлекает адрес возврата из стека вызовов и передает управление этому адресу.
1. Мой рисунок прав?
2. Если это правильно, то когда foo() завершится,
вытолкнуть адрес возврата из стека вызовов и передать управление этому адресу
, но как это может всплыть с обратного адреса? Потому что, когда foo заканчивается, текущий указатель стека указывает на локальный спам, верно?
ОБНОВИТЬ:
что если main() выглядит так:
int main()
{
spam();
foo();
}
тогда как должен выглядеть стек вызовов?
1 ответ
Ваш рисунок не правильный. Все локальные переменные стека для функции находятся ниже любых адресов возврата. В противном случае, как вы заметили, местные жители потеряются при вызове функции.
Должно быть так:
| parameters for foo() <int x> |
| return address of foo() |
| local of spam() <int c> |
| local of spam() <int b> |
| local of spam() <int a> |
| parameters for spam() <None> |
| return address of spam() |
| locals of main() <None> |
| parameters for main() <None> |
Я думаю, что путаница заключается в том, что вы считаете, что объявления переменных обрабатываются как операторы и выполняются по порядку. Фактически, компилятор обычно анализирует функцию, чтобы решить, сколько стекового пространства необходимо для всех локальных переменных. Затем он генерирует код для соответствующей настройки указателя стека, и эта настройка выполняется при входе в функцию. Любые вызовы других функций могут затем помещаться в стек без вмешательства в кадр стека этой функции.