Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
OC_03_LEC_Recursiya.doc
Скачиваний:
0
Добавлен:
01.04.2025
Размер:
154.62 Кб
Скачать

ASM: Лекция

Лекція №3.

Рекурсия

Слово рекурсия произошло от латинского «recursio» — «возвращение».

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

Самый очевидный тип рекурсии – бесконечная рекурсия. Она возникает в случае, когда процедура явно вызывает сама себя.

Рекурсивною називається така процедура, яка викликає сама себе.

Приклад. Нескінченна рекурсія.

Стан|статок| стека

000012FF70 0040102D RETURN to recurs

000012FF74 0040102D RETURN to recurs

000012FF78 0040102D RETURN to recurs

000012FF7C 0040102D RETURN to recurs

000012FF80 0040102D RETURN to recurs

000012FF84 0040102D RETURN to recurs

000012FF88 00401021 RETURN to recurs

000012FF8C 76F43C45 RETURN to mainin

TITLE Нескінченна|безконечна| рекурсія

INCLUDE Irvine32.inc

.code

main PROC

call recurs

exit

main ENDP

recurs PROC

call recurs

ret

recurs ENDP

END main

При каждом вызове рекурсивной процедуры из стека будет "забираться" 4 байта, поскольку CALL помещает в него адрес возврата из процедуры (адрес команды ret = 0040102D ). К команде RET управление никогда и не дойдет. Выполнение программы завершится аварийно из-за того, что место в стеке будет исчерпано.

Поэтому, внутри рекурсивной процедуры всегда должны быть заданы предвиденные условия для завершения ее работы. Цепочка вызовов начинает "разматываться" в обратном направлении: выполняются все команды RET, которые "зависли" к этому.

При кожному виклику рекурсивної процедури із стека" забирається" 4 байти, оскільки CALL поміщає в нього адресу повернення з процедури (адреса команди ret). До команди RET управління ніколи не дійде. Виконання програми завершиться аварійно через те, що місце в стеку буде вичерпано.

Тому, усередині рекурсивної процедури завжди мають бути задані умови для завершення її роботи. Ланцюжок викликів починає "розмотуватися" у зворотному напрямі: виконуються усі команди RET, які "зависли" до цього.

Вычисление факториала

В большинстве рекурсивных процедур используются стековые параметры, поскольку стек идеально подходит для сохранения временных данных во время рекурсивного процесса. Эти данные используются для завершения процесса рекурсии и возврата в вызвавшую подпрограмму.

Следующий пример, который мы должны рассмотреть, является своего рода "классикой жанра" рекурсивных процедур и связан с вычислением факториала целого беззнакового числа n.

Ей в качестве параметра передается исходное число n, факториал которого нужно вычислить.

Приклад. Обчислення n!

0! = 1

1! = 1

2! = 1! * 2

3! = 2! * 3

Формула в общем виде: n!=(n- 1)! * n

Условие завершения рекурсивного вызова: n=0. При этом процедура возвращает значение 1.

Выводы: рекурсивный алгоритм вычисления факториала числа n основан на том предположении, что для любого неотрицательного n мы можем вычислить факториал числа n - 1. Тогда процесс вычисления n! будет продолжаться до тех пор, пока n не станет равным нулю. По определению 0! равен 1.

Собственно значение выражения n! вычисляется во время обратного хода алгоритма, когда происходит накопление результатов каждого умножения.

Рассмотрим реализацию рекурсивного алгоритма вычисления факториала на языке ассемблера. Перед вызовом процедуры Factorial в стек помещается число n (целое беззнаковое число от 0 до 12). Значение факториала возвращается в регистре EAX. Поскольку используется один 32-разрядный регистр общего назначения EAX, то максимальное значение факториала, которое может в него поместиться, равно 479001 600, или 12!:

Формула в загальному вигляді: n!=(n - 1)! * n

Умова завершення рекурсивного виклику : n=0. При цьому процедура повертає значення 1.

Висновки: рекурсивний алгоритм заснований на тому, що для будь-кого додатнього n можно обчислити факторіал числа n - 1. Тоді процес обчислення n! триватиме до тих пір, поки n не стане рівним нулю. За визначенням 0! рівний 1. Значення вираження n! обчислюється під час зворотного ходу алгоритму.

Розробка алгоритму.

Проект складається з двох процедур. У головній - в стек проштовхується значення n і здійснюється виклик функції Factorial.

Виклик Factorial з головної процедури

esp

3

?

Разработка алгоритма процедуры Factorial

Перед викликом процедури Factorial в стек поміщається число n (ціле беззнакове число від 0 до 3). Значення факторіалу повертається в регістрі EAX. Оскільки використовується один 32-розрядний регістр загального призначення EAX, томаксимальне значення факторіалу, яке може в нього поміститися, дорівнює 479001 600, або 12!:

Дії, виконувані процедурою обчислення факторіалу, розіб'ємо на 4 частини:

  1. Дії, що виконуються до перевірки умови виходу з процедури, : фіксація області початку локальних змінних.

До виклику sum з головної процедури

ebp

esp

Адрес exit

3

?

  1. Визначення чергового значення n, занесеного в стек на попередньому етапі. Значення розраховується відносно регістра ebp.

  2. Перевірка умови. Якщо n=0, рекурсивний виклик процедури буде завершений. Повертане процедурою значення (в даному випадку, 1) буде занесено в eax. Вихід з процедури буде здійснимо стандартним чином:

mov esp, ebp

pop ebp

Якщо n!=0, зменшуємо значення n на одиницю і проштовхуємо йогов стек. Потім робимо виклик цієї ж процедури.

  1. Дії після повернення з процедури. Розраховуємо поточне значення n відносно регістра ebp і множимо його на вміст регістра eax.

ebp

factorial

Адрес возврата

1

factorial

ebp

Адрес возврата

2

factorial

ebp

Адрес exit (main)

3

main

?