
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
|
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 частини:
Дії, що виконуються до перевірки умови виходу з процедури, : фіксація області початку локальних змінних.
До виклику sum з головної процедури
-
ebp
esp
Адрес exit
3
?
Визначення чергового значення n, занесеного в стек на попередньому етапі. Значення розраховується відносно регістра ebp.
Перевірка умови. Якщо n=0, рекурсивний виклик процедури буде завершений. Повертане процедурою значення (в даному випадку, 1) буде занесено в eax. Вихід з процедури буде здійснимо стандартним чином:
mov esp, ebp
pop ebp
Якщо n!=0, зменшуємо значення n на одиницю і проштовхуємо йогов стек. Потім робимо виклик цієї ж процедури.
Дії після повернення з процедури. Розраховуємо поточне значення n відносно регістра ebp і множимо його на вміст регістра eax.
-
ebp
factorial
Адрес возврата
1
factorial
ebp
Адрес возврата
2
factorial
ebp
Адрес exit (main)
3
main
?