Chapter_07 (1110559), страница 13
Текст из файла (страница 13)
Поэтому лучше преобразовать этот операторв такой эквивалентный вид:Factorial:=1;if N>1 then Factorial:=N*Factorial(N-1)Теперь приступим к написанию функции Factorial на Ассемблере:Factorial proc near; стандартные соглашение о связяхpush bpmovbp,sp; база стекового кадраpush dxNequword ptr [bp+4]; фактический параметр Nmovax,1; Factorial(N<=1)cmpN,1jbeVozvmovax,Ndecax; N-1push axcall Factorial; РекурсияmulN;ax*N = Factorial(N-1)*NVozv: popdxpopbpret2Factorial endpВ качестве примера рассмотрим вызов этой функции Factorial для вычисления факториалачисла 5.
Такой вызов можно в основной программе сделать, например, следующими командами:movax,5push axcall Factorialoutword axНа рис. 7.6 показан вид стека после того, как произведён первый рекурсивный вызов функции,заметьте, что в стеке при этом два стековых кадра.Начало стека SS →Вершина стека SP →Начало второго кадра →Конец первого кадра →Начало первого кадра →Значение регистра dxЗначение регистра bpАдрес возврата IPN=4Значение регистра dxЗначение регистра bpАдрес возврата IPN=5Рис. 7.6. Два стековых кадра функции Factorial.В качестве ещё одного примера рассмотрим реализацию с помощью процедуры следующего алгоритма: задана константа N=30000, найти скалярное произведение двух массивов, содержащих поN беззнаковых целых чисел.41NScal := ∑ X[i] * Y[i]i =1На языке Паскаль это можно записать, например, следующим образом:1Const N=30000;Type Mas = array[1..N] of word;VarA,B: Mas; S: word;Procedure SPR(var X,Y: Mas; N: integer; var Scal: word);Var i: integer;Begin Scal:=0; for i:=1 to N do Scal:=Scal+X[i]*Y[i] end;Перед реализацией этой процедуры с именем SPR на Ассемблере (со стандартными соглашениями о связях) необходимо решить следующие вопросы.
Во-первых, сделаем нашу процедурудальней, чтобы она могла располагаться в любом сегменте памяти нашей программы. Во-вторых,массивы A и B оба не поместятся в один сегмент данных, поэтому нам придётся описать два сегментаданных D1 и D2 и поместить в один из них массив A, а в другой сегмент – массив B:ND1ASD1D2BD2equ30000segmentdwN dup (?)dw?endssegmentdwN dup (?)endsПри передаче таких массивов по ссылке нам придётся заносить в стек дальний адрес каждогомассива в виде двух чисел <сегмент,смещение>. То же самое придётся делать и для передаваемой по ссылке переменной S, куда будет помещаться вычисленное значение скалярного произведения.
Далее надо решить, как информировать обратившуюся к процедуре основную программу о том,что скалярное произведение не может быть получено правильно, так как не помещается в переменную S. Давайте, например, выделим значение 216-1 (это знаковое число –1) для случая переполнения результата. Эта проблема является типичной в практике программирования: желательно, чтобыкаждая процедура и функция выдавали код возврата, который показывает, правильно ли завершилась их работа. Таким образом, значение –1 свидетельствует об ошибке, а все остальные значенияпеременной S будут означать правильное завершение работы нашей процедуры (т.е.
правильноезначение скалярного произведение, равное 216-1 мы тоже, к сожалению, объявим ошибочным).Напишем теперь фрагмент программы для вызова процедуры скалярного произведения:movpushmovpushmovpushmovpushmovpushmovpushmovpushcall1ax,D1axax,offset Aax; Полный адрес массива Aax,D2axax,offset Bax; Полный адрес массива Bax,Nax; Длина массивовax,D1axax,offset Sax; Полный адрес SSPRЭто записано на стандарте Паскаля, на языке Турбо-Паскаль так написать нельзя, т.к. массивы A и B непоместятся в один сегмент данных, а размещать статические переменные в разных сегментах Турбо-Паскальне умеет.42Для такого вызова при входе в процедуру стековый кадр будет иметь вид, показанный на рис.7.7.Вершина стека SP →Начало стекового кадра →IP адреса возвратаCS адреса возвратаАдрес S в D1Адрес сегмента D1Число элементов NАдрес массива B в D2Адрес сегмента D2Адрес массива A в D1Адрес сегмента D1Рис.
7.7. Стековый кадр при входе в процедуру скалярного произведения.Теперь опишем нашу дальнюю процедуру:SPRproc farpush bp; база стековогоmovbp,sp; кадра; сохранение остальных регистровpush dspush es.186pusha ;в стек ax,cx,dx,bx,sp,bp,si,disubbx,bx; локальная суммаmovcx,[bp+10]; Длина массивов Nmovds,[bp+18]; Сегмент D1movsi,[bp+16]; Адрес Amoves,[bp+14]; Сегмент D2movdi,[bp+12]; Адрес BL:movax,[si]; A[i]mulword ptr es:[di]; A[i]*B[i]jcErr; при переполненииaddbx,axjcErr; при переполненииadddi,2addsi,2loop LVozv: movds,[bp+8]; Сегмент D1movsi,[bp+6]; Адрес Smov[si],bx; Результат в S; восстановление регистровpopa ;из стека ax,cx,dx,bx,sp,bp,si,dipopespopdspopbpret2*7; Очистка 7 слов из стекаErr: movbx,-1;Код ошибкиjmpVozvSPRendpВ этом примере для экономии текста программы мы использовали команды pusha иpopa из языка команд старших моделей нашего семейства ЭВМ, о чём предварительно предупредили Ассемблер директивой .186 .
Это сделано только для иллюстрации возможностей старшихмоделей нашего семейства ЭВМ, в дальнейшем мы не будем использовать такие команды, оставаясьв рамках языка машины младшей модели.43На этом мы закончим изучение процедур в языке Ассемблера.Вопросы и упражнения1.2.3.4.5.6.7.8.9.10.11.12.13.14.Почему в программе не рекомендуется использовать стек полного размера 216 байт?Почему при работе программы в текущем стеке всегда должно оставаться некоторое количество свободного места?Как Ассемблер определяет, какой из сегментов, описанных в программе, является начальнымсегментом стека?Какое значение имеет регистр SP перед выполнением первой команды программы?Можно ли использовать в программе команду вызова процедуры call в виде call ax ?Как Ассемблер определяет, код машинной операции длинного или короткого возврата надоподставить вместо конкретной команды ret ?Опишите, как будет выполняться команда близкого возврата ret 17 ?Почему в языке машины не реализована команда pop CS ?Чем понятие процедуры в Ассемблере отличается от понятия процедуры в Паскале?Обоснуйте необходимость принятия стандартных соглашений о связях между процедурой ивызывающей её программой.Что такое прямой и обратный порядок передачи параметров в процедуру?Что такое стековый кадр?Напишите дальнюю функцию со стандартным соглашением о связях, которая получает какпараметр целое беззнаковое число, и вычисляет 32-хбитное значение факториала от этого числа.Обоснуйте, какие из пунктов стандартных соглашений о связях необходимы для обеспечениярекурсивных вызовов процедур и функций..