Реализация рекурсивных процедур
В общем-то ничего необычного в этом коде нет. Передача параметров в рекурсивную процедуру производится через стек. При этом необязательно все данные помещать в стек. Возможен вариант, когда локальные данные определяют в сегменте данных или в выделенной динамической области памяти, а в стек помещается только указатель на них. В любом случае, начало рекурсивной процедуры будет содержать код пролога, подобный приведенному в программе выше:
fact ргос push bp mov bp.sp mov cx.[bp+4]
Смысл этого фрагмента легче понять, наблюдая поведение программы вычисления факториала в отладчике. Как сказано выше, перед вызовом процедуры в стек помещаются данные (или указатель на них), информация о местонахождении которых должна быть сохранена в интересах как вызывающей, так и вызываемой процедуры. В нашем случае в процедуру fact передается переменная факториала. После этого производится вызов процедуры, в результате чего в стек помещается адрес возврата. В вызванной процедуре к данным переменным необходимо получить доступ. Для этого предназначен регистр ВР. Перед использованием его содержимое должно быть также сохранено в стеке. Для первого вызова его значение несущественно. В этот момент весь предыдущий контекст работы программы сохранен. Команда mov bp.sp загружает в регистр ВР указатель на текущую вершину стека, после чего можно обращаться к данным, переданным в процедуру.
По сути, сейчас мы с вами сформировали кадр стека. Следующий рекурсивный вызов этой функции придает действию сохранения регистра ВР особый смысл. Команда push bp сохраняет в стеке адрес кадра стека для предыдущего вызова рекурсивной процедуры. Теперь для выхода из процедуры достаточно выполнить приведенные ниже команды эпилога, позволяющие корректно отработать обратную цепочку возврата в основную программу: будет рассмотрена в этой главе ниже. Далее сравним работу функций DrawPattern i и DrawPattern 1.
Вызов функции DrawPattern_1 из основной программы осуществляется следующим фрагментом кода.
:prg3_1.asm – фрагмент оконного приложения, вызывающего рекурсивную процедуру :DrawPattern_l объявление пользовательских процедур (из maket_dll.DLL) extrn DrawPattern_l:PROC extrn DrawPattem_2:PR0C .data определение констант для фигуры "Узор из окружностей" р dd 5;порядок узора г dd 60:радиус окружности y_Pdd 140 начальная у-координата центра окружности х_Р dd 200 начальная х-координата центра окружности .code обработка сообщений от меню MenuProc proc arg (a@hwnd: DWORD. №wparam: DWORD, @(ahdc: DWORD.@@hbrush: DWORD uses eax.ebx mov ebx.@@wparam:в bх идентификатор меню onpbx.IDMJ)LL_LACESJ je @@idmdlllaces_l cmpbx.IDM_DLLJ_ACES_2 je @@idmdlllaces_2 jmp@@exit e@1 chndl 11 aces_l: ;рисуем узор из окружностей, рекурсивная функция для рисования находится ;в DLL-библиотеке: ;DrawPattern_l(hwnd.hdc,x.y.r.p) – функция не работает с локальными переменными: push p:порядок узора push r:радиус окружности push y_P:у-координата центра окружности push x_P;х-координата центра окружности push memdc:контекст устройства push @@hwnd call DrawPattern_1 jmp@@exit:……… Фрагмент файла maket_dll.DLL, содержащий процедуру DrawPattern_1, приведен ниже: iinaket_dn.DLL – фрагмент DLL-библиотеки, содержащей рекурсивную процедуру DrawPatternJ объявление процедур DLL-библиотеки общедоступными publicdll WriteCon publicdll DrawPatternJ publicdll DrawPattern_2 .code DrawPatternJ proc :DrawPattern_l – рекурсивная процедура рисования узора:(без использования локальных переменных) arg @@hwnd:dword.@@hdc:dword.@@x:dword.@@y:dword.@@r:dword.@@p:dword :рисуем окружность :рекурсивно вызываем DrawPattern_l(hwnd.hdc,x.y.r,p) :BOOL Ellipse(HDC hdc .int nLeftRect .int nTopRect .int nRightRect.int nBottomRect): :готовим параметры в стеке для вызова Ellipse call Ellipse:рисуем окружность:и еще четыре меньшего порядка dec @@p стр @@р, 0 je @@End_Draw shr@@r,l – . делим на 2 :готовим параметры в стеке для вызова DrawPatternJ call DrawPattern_l:готовим параметры в стеке для вызова DrawPattern_l call DrawPatternJ:готовим параметры в стеке для вызова DrawPatternJ call DrawPattern_l:готовим параметры в стеке для вызова DrawPatternJ. call DrawPatternJ @@End_Draw: генерация сообщения WM_PAINT для вывода изображения на экран call InvalidateRect endp DrawPatternJ