Работа «вызовов функций» в стеке?

0
main() calling f1(), f1() calling f2(), f2 calling f3(), f3() calling f4() and so on...

Функция вызывает другую функцию, и цепочка продолжается.

|        |
| f4()   |
| f3()   |
| f2()   |
| f1()   |
| main() |
__________

Когда вызывается функция, создается структура с именем " Активация", которая содержит информацию, связанную с этим вызовом. Эта запись активации каждой функции вставляется в стек, называемый стекми программ или стек времени выполнения. У меня нет никакой идеи ни об записи активации, ни о времени выполнения. Моя проблема:

  1. Функция вызывает другую функцию, и цепочка продолжается. Но как долго? как долго эти вложенные вызовы могут продолжаться? И от чего они зависят? Это ОС или бит архитектуры?

  2. Что содержит запись активации? Какова его структура? Также передаются локальные данные?

  3. Какие параметры установлены для такого типа функций (которые имеют несколько вложенных вызовов или рекурсивных функций)? Я имею в виду, как знать заранее, чтобы избежать переполнения.

  • 1
    Там нет стандартного метода для обнаружения или предотвращения переполнения стека. Это вызвало много боли на протяжении многих лет
  • 0
    это может цепочка, пока есть место в стеке
Теги:
function
memory
stack

3 ответа

2

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

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

Поскольку указатель стека часто является одним из сохраненных регистров, во многих средах можно "ходить" по стеклу, используя регистр указателя кадра, чтобы найти сохраненное значение указателя стека и указателя кадра, как оно существовало в вызывающем, повторяя при необходимости, Если активирован параметр компилятора, такой как "упущение указателя кадра" (или система не использует регистр указателя кадра), это можно сделать, используя указатель стека в сочетании с метаданными отладки.

Если вы пишете такой алгоритм ходьбы стека или вычисляете общее использование стека, вам нужно иметь дело с компоновкой стека. Это также полезно для понимания того, как переполнение буфера в переменной стека может привести к перезаписи адреса возврата в эксплоите "return to libc".

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

1

Если ваша функция создает локальные переменные в стеке, вы увидите, что адрес этой переменной изменяется по мере того, как стек становится глубже. Вы можете получить общее представление о том, сколько стека вы использовали, отслеживая это число по мере его изменения. Сделайте это для своего образования - не в фактическом коде. Что-то вроде этого:

#include <stdio.h>

int foo(void);
char *sp;

int main(void){
  char a;
  sp = &a;
  foo();
  return 0;
}

int foo(void) {
  char c;
  long depth;
  depth = sp - &c;
  if(depth < 1000) {
    printf("depth is %ld\n", depth);
    foo();
  }
  return 0;
}

На моей машине кажется, что стек увеличивается на 48 байт для каждого последующего вызова foo().

  • 0
    Различные типы ошибок здесь: Разница между двумя указателями не является указателем. Вы не можете вычесть int* из char* . И вы не хотите делать арифметику указателей на int* если вас интересует количество байтов.
  • 0
    @benvoigt спасибо за указание на это - попытался убрать это. Теперь лучше?
Показать ещё 1 комментарий
0

"Активационная запись" является синонимом "фрейма стека" - мне пришлось посмотреть это, поскольку я не знаком с этим термином.

  1. В большинстве систем стек начинается с максимального адреса памяти, а затем растет. Определенный объем памяти выделяется для стека (и для каждого потока создается стек). Фреймы могут быть добавлены в стек до тех пор, пока он не достигнет предела, а затем он станет переполнением стека. Обычно это происходит, когда рекурсивная функция вызывает слишком много раз - для нерекурсивного вызова очень редко встречается вызов (одна возможность - это выделение большого объема памяти в стеке).
  2. Содержимое фрейма стека подробно описано в wikipedia (http://en.wikipedia.org/wiki/Call_stack) - обычно он содержит адрес возврата вызова функции и пространство для каждого выделенного стека (ака "локальный" или "автоматический")).
  3. Как правило, программа не может проверять свои собственные стеки вызовов во время выполнения. Единственный способ избежать - следить за тем, чтобы подсчет глубины был следующим:

function beginFoo() {

    foo(0);
}

function foo(int depth) {
    if( depth > 10 ) return; // avert possible stack-overflow
    foo(depth + 1);
}

Ещё вопросы

Сообщество Overcoder
Наверх
Меню