путаница со стеком вызовов функций

Согласно Вики:

вызывающая сторона помещает адрес возврата в стек, а вызываемая подпрограмма, когда она завершается, извлекает адрес возврата из стека вызовов и передает управление этому адресу.

Фото из Вики:

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

Я не совсем понимаю это. Скажем, у меня есть программа 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();
}

тогда как должен выглядеть стек вызовов?


person Alcott    schedule 21.09.2011    source источник


Ответы (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>  |

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

person David Heffernan    schedule 21.09.2011
comment
да, сэр, я согласен с вами. Но в spam() foo() вызывается перед ‹int c = b›, верно? Тогда куда мне поместить int c в стек вызовов? Также ниже обратный адрес? - person Alcott; 21.09.2011
comment
Резервирование стека локальных переменных обычно делается раз и навсегда в начале выполнения функции. Таким образом, a, b и c являются зарезервированным пространством стека до вызова foo. - person David Heffernan; 21.09.2011
comment
Понял, сэр. Что, если я вызову spam() и foo() в main (см. ОБНОВЛЕНИЕ)? Как будет выглядеть стек вызовов? Вы имеете в виду, что размер кадра стека функции является суммой размера его локальных переменных? - person Alcott; 21.09.2011
comment
Дэвид прав в том, что вы, кажется, неправильно понимаете объявления переменных как операторы, выполняемые по порядку. Любой вызов функции поместит только один кадр стека в стек вызовов. Этот кадр стека уже будет иметь все пространство, необходимое для функции (как определено компилятором). Каждый кадр стека будет иметь адрес возврата, когда функция вернется, и установленное количество места для всех параметров и локальных переменных. - person Lncn; 21.09.2011
comment
Кажется, я уже ответил на это. Я не хочу вдаваться в затянувшиеся серьезные правки вопроса, чтобы задать дополнительные вопросы. Однако в своем обновлении вы вызываете спам, отправляя возвращаемое значение, вызывая функцию, выталкивая возвращаемое значение и переходя туда. Затем вы вызываете foo точно таким же образом. Наконец, ваш обновленный вопрос не полностью определен, потому что стек вызовов будет выглядеть по-разному на разных этапах выполнения. - person David Heffernan; 21.09.2011