Анализ подпрограмм
Анализ подпрограммы — это пошаговое прослеживание того, какие значения получают параметры и локальные переменные, в каком порядке выполняются вызовы и какое значение возвращается. Такой способ позволяет надёжно решать задания, где нужно определить результат работы программы или последовательность вызовов.
Что происходит при вызове подпрограммы
Подпрограмма — именованный фрагмент программы, который можно вызвать из другой части программы. Перед анализом полезно повторить устройство процедуры и функции: процедура обычно выполняет действие, а функция дополнительно возвращает результат.
Вызов подпрограммы — передача управления её заголовку с конкретными аргументами. При вызове фактические параметры сопоставляются с формальными, создаются локальные переменные, затем выполняются команды тела. После завершения управление возвращается в точку вызова.
Формальный параметр записан в объявлении подпрограммы, а фактический параметр — выражение или переменная, указанная при вызове. Например, в f(2 * x) выражение 2 * x является фактическим параметром, а имя a в function f(a) — формальным.
| Этап | Что проверять |
|---|---|
| 1. Вход | Какие значения переданы параметрам |
| 2. Локальная область | Какие переменные созданы внутри подпрограммы |
| 3. Выполнение | В каком порядке идут операторы и вложенные вызовы |
| 4. Выход | Что изменилось и какое значение возвращено |
| 5. Продолжение | С какой команды возобновляется вызывающая программа |
Если параметр передаётся по значению, подпрограмма получает копию. Изменение этой копии не меняет исходную переменную. При передаче по ссылке параметр связан с исходной переменной, поэтому присваивание внутри подпрограммы может изменить её.
Области видимости и локальные переменные
Локальная переменная существует только во время выполнения той подпрограммы, где она объявлена. После возврата управление покидает подпрограмму, и её значение нельзя считать доступным в вызывающей части. Переменная с таким же именем в другой подпрограмме — это обычно другая переменная.
При чтении имени сначала ищут переменную в текущей подпрограмме. Если её нет, проверяют внешнюю или глобальную область. Локальная переменная с совпадающим именем скрывает внешнюю глобальную переменную.
Для каждой активной подпрограммы удобно мысленно заводить отдельную строку состояния: значения параметров, локальных переменных и текущую команду. Если функция вызывает другую функцию, состояние первой не исчезает — оно временно откладывается до возврата.
Не переносите значение локальной переменной из одного вызова в другой. Даже если подпрограмма вызывается дважды с одинаковым именем локальной переменной, это два разных экземпляра переменной, если только язык явно не задаёт сохранение состояния.
Порядок выполнения и граф вызовов
Структура программы задаёт порядок команд: сначала выполняется очередной оператор, затем — вызванная им подпрограмма. Если функция A вызывает B, а B вызывает C, выполнение идёт по цепочке A → B → C, а после завершения C управление возвращается в B, затем в A.
Граф вызовов — схема, в которой вершины соответствуют подпрограммам, а направленная дуга \(A \to B\) означает: подпрограмма \(A\) может вызвать подпрограмму \(B\). Граф показывает связи между подпрограммами, но не всегда полностью задаёт порядок: если два вызова идут последовательно, их порядок нужно прочитать в тексте тела.
Для ручного решения полезно строить не только граф, но и стек вызовов. В него записывают текущую цепочку, например A → B → D. Возвращаясь из D, удаляют последний элемент и продолжают B с команды после вызова D.
Функция A вызывает B, затем C. Внутри B вызывается D. Каков порядок первого полного прохода при последовательном выполнении?
Алгоритм трассировки
В экзаменационном задании не пытайтесь сразу вычислить всё выражение. Разделите работу на состояния и фиксируйте каждое изменение. Особенно важно различать присваивание и возврат: команда x := f(a) сначала полностью выполняет f(a), а затем записывает возвращённое значение в x.
- Выпишите начальные значения всех переменных.
- Найдите первый выполняемый вызов и передайте ему фактические значения.
- Создайте отдельную запись параметров и локальных переменных этого вызова.
- Выполняйте команды сверху вниз, раскрывая вложенные вызовы только в момент их появления.
- Запишите результат
returnили присваивания имени функции. - Вернитесь к вызывающей подпрограмме и продолжите со следующего оператора.
- После завершения основного вызова определите требуемый вывод или итоговую переменную.
Для сложного кода используйте столбцы: вызов, параметры, локальные переменные, действие, возврат. Номер вызова помогает не перепутать два одинаковых обращения к одной функции.
Разобранный пример
Рассмотрим функцию, которая изменяет параметр и возвращает сумму локальной переменной с результатом вложенного вызова. Передача параметров выполняется по значению.
1function F(a) 2 b := a + 1 3 if a > 1 then 4 return b + F(a - 2) 5 else 6 return b 7 8x := F(5)
Нужно найти значение x, не потеряв параметры разных рекурсивных вызовов. Каждый вызов функции получает собственные локальные переменные.
Показать краткое решение Ответ
Цепочка вызовов: F(5) → F(3) → F(1). Значения локальной переменной b: 6, 4, 2. Возвраты идут в обратном порядке: 2, затем 6, затем 12. Итог: x = 12.
Не складывайте все значения b автоматически: каждое складывание происходит только там, где написано return b + F(...). Также нельзя считать, что после возврата внутреннего вызова внешний a изменился: при передаче по значению он остаётся равен своему исходному значению.
Формулы и контроль результата
Если подпрограмма возвращает результат через выражение, результат внешнего вызова вычисляется после завершения внутреннего. Для последовательных вызовов полезно записывать зависимость справа налево.
В этой записи \(g(a)\) — базовый случай, \(h(a)\) — часть результата текущего вызова, а \(q(a)\) — новый параметр. Для обычных, нерекурсивных подпрограмм аналогичный контроль выполняют по цепочке возвратов: сначала считают самый глубокий вызов, затем подставляют его результат.
Вызов выполняется до присваивания его результата. Локальные переменные принадлежат конкретному вызову. Возврат идёт к оператору, который следует сразу после вызова. При анализе сначала раскрывают вложенный вызов, затем продолжают внешнюю подпрограмму.
Быстрая проверка
Главное
- Граф вызовов показывает, какие подпрограммы вызывают друг друга; порядок команд нужно читать по телу подпрограммы.
- Для каждого вызова отдельно отслеживайте параметры и локальные переменные.
- Вложенный вызов завершается раньше присваивания его результата и продолжения внешней подпрограммы.
- При возврате двигайтесь по стеку вызовов в обратном порядке.
- При передаче по значению изменения параметра не меняют исходную переменную; при передаче по ссылке могут менять.