@martuwka

Что такое call stack?

При отладке программы в Microsoft Visual Studio наткнулся на такое поле, как Call Stack. Может ли кто-то объяснить чуть подробнее, что это, зачем оно и как им пользоваться. Английский не очень хорошо знаю, поэтому не искал по англоязычным сайтам. Может кто с опытных объяснит или даст ссылку почитать об этом (только по-русски), буду благодарен.
  • Вопрос задан
  • 11241 просмотр
Решения вопроса 2
Часть памяти компьютера отведена под стек.
Когда вы вызываете какую-либо функцию в вашем коде, параметры функции кладутся в стек (зачастую, но есть и другие способы). Так же необходимо знать, куда вернуться из функции - в стек кладётся адрес возврата.

Call Stack - окно, в котором отображаются все уровни вложенности вызовов функций.
Там вы можете узнать, в какой функции вы сейчас находитесь, из какой функции она была вызвана и так далее рекурсивно наверх вплоть до точки входа в программу.
Собственно call stack вычисляется на основе информации из стека.
Ответ написан
Комментировать
Li4ik3
@Li4ik3
Стек вызовов или call stack — это список всех активных функций, которые вызывались, до текущей точки выполнения. В стеке вызовов отображается каждая вызываемая функция, а также строка, которая в данный момент выполняется в программе. Всякий раз, при вызове новой функции - она добавляется в самый top of call stack :) Как только выполнение текущей функции подходит к финишу, она удаляется из топа в стеке и контроль переходит к функции ниже (второй по счету). В общем источник, почитать о call stack можно здесь.
Ответ написан
Пригласить эксперта
Ответы на вопрос 3
saboteur_kiev
@saboteur_kiev Куратор тега Программирование
software engineer
Когда-то был оператор goto, и программы выглядели так

10 let a = 5
20 if a =5 then goto 100
30 print "Вот и все, ребята"
40 exit

100 print "Да, наше a = 5"
110 goto 30


Мы что-то проверяем, если проверка успешна, вызываем какое-то действие. Затем возвращаемся назад.
Но такой вариант оказался неудобным, если это какое-то действие нужно вызывать из разных мест, а потом возвращаться именно в эти разные места. Поэтому сейчас используется не goto, а call (вызов), который кладет в стек адрес текущего места выполнения и переходит в подпрограмму. В конце подпрограммы по команде return, он берет из стека адрес и по нему возвращается назад.
Так как в стек можно положить что-то еще, то можно внутри вызванной подпрограммы вызвать другую подпрограмму, и рекурсивно вызывать столько раз сколько нужно. Потом все call-ы будут красиво закрыты return-ами в обратном порядке.
main () {
  call program1;
  call program2;
}

program1 () {
call program 3;
return;
}

program2 () {
call program3;
return;
}

program3 () {
return;
}

В данном варианте у нас работает так:
1. из основной части main, вызывается program1 (в стек кладется адрес этой)
2. из вызванного program1 вызывается program3 (в стек добавляется адрес этой команды, там уже две)
3. из program3 мы возвращаемся, беря последнее значение из стека (возвращаемся в program1)
4. снова возвращаемся, беря адрес из стека и попадаем в main
5. тоже самое с вызовом program2-program3-program2-main

Стек обычно растет сверху вниз, каждая команда return берет самый последний нижний адрес и возвращается по нему, что позволяет создавать множество вложенных вызовов, и рекурсивно с ними работать.
Но не нужно забывать, что стек не бесконечен. десять или сто вызовов вообще ни о чем на современных компах, но миллион или миллиард, умножить на размер адреса (например 4 байта), может занять мегабайты и гигабайты.
Ответ написан
Комментировать
Rou1997
@Rou1997
Чтобы при остановке выполнения (на точке останова или при выбросе исключения) узнать, в какой функции (и возможно с номером строчки кода) произошла остановка, со вложенностью.

Пример:
void foo2() {
foo3(5 / 0);
}

void foo1() {
foo2();
}

void main() {
foo1();
}


Call stack trace of exception:

foo2
foo1
main


Английский не очень хорошо знаю,

Причем здесь английский, надо было просто проверить это окно в работе.
Ответ написан
Комментировать
@Mercury13
Программист на «си с крестами» и не только
Call stack — это стек вызовов, аппаратная фишка всех крупных процессоров.

int foo()
{
   int y = ...;
   if (y < 0)
     y = -y;
   return y;
}

void bar()
{
   int x = foo();
   if (x != 0)
      bar();
   foo();
}

void main()
{
  int z = foo();
  bar();
}


Вот мы заходим в функцию main(). В это время в памяти появляется переменная z. А затем в foo() — появляется ещё и переменная y. А затем выходим из foo — y исчезает. Что у нас получается? Либо в конец добавляем одну или несколько локальных переменных, либо с того же конца удаляем их. Какая структура данных подходит для этого? Стек: добавлять в конец, удалять из конца. Всё это, напоминаю, реализовано в x86 аппаратно, одним сегментом, двумя регистрами (esp = 32-bit stack pointer и ebp = 32-bit base pointer) и несколькими командами (push, pop, call, ret…).

Локальные переменные, принадлежащие одной функции, называются стековый фрейм. При этом фреймов одной функции может быть несколько, если она вызывает сама себя, прямо или опосредованно — как функция bar().

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

Поскольку стек аппаратный, его нельзя расширять; можно только до начала программы/потока указать, сколько памяти нам хватит. И потому часта ошибка «переполнение стека».
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы