7_Язык Ассемблера (В.Г. Баула - Введение в архитектуру ЭВМ и системы программирования), страница 12
Описание файла
Файл "7_Язык Ассемблера" внутри архива находится в папке "В.Г. Баула - Введение в архитектуру ЭВМ и системы программирования". PDF-файл из архива "В.Г. Баула - Введение в архитектуру ЭВМ и системы программирования", который расположен в категории "". Всё это находится в предмете "практика расчётов на пэвм" из 1 семестр, которые можно найти в файловом архиве МГУ им. Ломоносова. Не смотря на прямую связь этого архива с МГУ им. Ломоносова, его также можно найти и в других разделах. .
Просмотр PDF-файла онлайн
Текст 12 страницы из PDF
записывая в стек) фактические параметры. Затем команда call помещает в стек адрес возврата (это одно слово для близкой процедуры и два – для дальней) и передаёт управление наначало процедуры. Далее уже сама процедура или функция продолжает построение стекового кадра,размещая в нём свои локальные переменные, в частности, для хранения старых значений изменяемых регистров.Заметим, что если построением стекового кадра занимаются как основная программа, так и процедура (функция), то полностью разрушить (очистить) стековый кадр должна сама процедура (функция), так что при возврате в основную программу стековый кадр будет уже уничтожен.1Перепишем теперь нашу последнюю "хорошую" программу с языка Паскаль на Ассемблер с использованием стандартного соглашения о связях. Будем предполагать, что передаваемый по ссылкеадрес фактического параметра-массива занимает одно слово (т.е.
является близким адресом – смещением в сегменте данных). Для хранения стекового кадра (локальных переменных функции) дополнительно зарезервируем в стеке, например, 32 слова. Ниже показано возможное решение этойзадачи.include io.asmdata segmentXdw100 dup(?)Ydw200 dup(?)Sumdw?data endsstack segment stackdw64 dup (?); для системных нуждdw32 dup (?); для стекового кадраstack endscode segmentassume cs:code,ds:data,ss:stackSumma proc near; стандартные соглашение о связяхpush bpmovbp,sp; база стекового кадраpush bxpush cx;запоминание используемых регистровsubsp,2; порождение локальной переменнойSequword ptr [bp-6]; S будет именем локальной переменнойmovcx,[bp+4]; cx:=длина массиваmovbx,[bp+6]; bx:=адрес первого элементаmovS,0; сумма:=0L:movax,[bx];нельзя add S,[bx]addS,ax;так как нет формета память-памятьaddbx,2loop Lmovax,S; результат функции на axaddsp,2; уничтожение локальной переменной Spopcxpopbxpopbp; восстановление регистров cx, bx и bpret2*2; возврат с очисткой стека от фактических параметровSumma endpstart:movax,data1При обратном порядке записи фактических параметров в стек (как в языке С) удаление из стека фактических параметров обычно делает не процедура, а основная программа, т.к.
это легче реализовать для переменного числа параметров (основной программе, в отличие от процедуры, легче определить, сколько параметровзаписано в стек для конкретного вызова). Плохо в этом случае то, что такое удаление приходится выполнять нев одном месте (при выходе из процедуры), а в каждой точке возврата из процедуры, что существенно увеличивает размер кода (в программе может быть очень много вызовов одной и той же процедуры).38movds,ax; здесь команды для ввода массивов X и Уmovax, offset X; адрес начала Xpush ax; первый фактический параметрmovax,100push ax; второй фактический параметрcall SummamovSum,ax; сумма массива Xmovax, offset Y; адрес начала Ypush ax; первый фактический параметрmovax,200push ax; второй фактический параметрcall SummaaddSum,ax; сумма массивов X и Youtint Sumnewlinefinishcode endsend startПодробно прокомментируем эту программу.
Первый параметр функции у нас передаётся поссылке, а второй – по значению, именно так мы и записываем эти параметры в стек. После выполнения команды вызова процедуры call Summa стековый кадр имеет вид, показанный на рис. 7.4.После полного формирования стековый кадр будет иметь вид, показанный на рис. 7.5. (справа наэтом рисунке показаны смещения слов в стеке относительно значения регистра bp).Начало стека SS →Вершина стека SP →Начало стекового кадра →Адрес возврата IPЧисло элементов NАдрес начала массиваРис. 7.4.
Вид стекового кадра при входе в функцию Summa.Отметим далее то особое значение, которое имеет индексный регистр bp при работе со стеком.В архитектуре нашего компьютера это единственный индексный регистр, который предписывает поумолчанию для команд формата регистр-память осуществлять запись и чтение данных из сегментастека. Так команда нашей программыmov cx,[bp+4]; cx:=длина массивачитает в регистр cx слово, которое расположено по физическому адресуАфиз = (SS*16 + (4 + <bp>)mod 216)mod 220,а не по адресуАфиз = (DS*16 + (4 + <bp>)mod 216)mod 220,как это происходит при использовании на месте bp любого другого индексного регистра, т.е. bx, siили di.Таким образом, если установить регистр bp внутрь стекового кадра, то его легко использовать вкачестве базы для доступа к локальным переменным процедуры или функции. Так мы и поступили внашей программе, поставив регистр bp (по сути, это ссылочная переменная в смысле Паскаля) примерно на средину стекового кадра.
Теперь, отсчитывая смещения от значения регистра bp вниз, например [bp+4], мы получаем доступ к фактическим параметрам, а, отсчитывая смещение вверх –доступ к сохранённым значениям регистров и локальным переменным. Например [bp-6]являетсяадресом локальной переменной, которую в нашей программе на Ассемблере мы, повторяя программу на Паскале, назвали именем S (см. рис. 7.5).
Теперь понятно, почему регистр bp часто называютбазой стекового кадра (base pointer).39Начало стека SS →Вершина стека SP →База стекового кадра bp →Начало стекового кадра →Локальная переменная SЗначение регистра cxЗначение регистра bxЗначение регистра bpАдрес возврата IPЧисло элементов NАдрес начала массиваbp-6bp-4bp-2bp+0bp+2bp+4bp+6Рис. 7.5. Вид полного стекового кадра (справа показаны смещенияслов кадра относительно значения регистра bp).Обратите внимание, что локальные переменные в стековом кадре не имеют имён, что можетбыть не совсем удобно для программиста. В нашем примере мы присвоили локальной переменнойимя S при помощи директивы эквивалентностиS equ word ptr [bp-6]И теперь всюду вместо имени S Ассемблер будет подставлять выражение word ptr [bp-6], которое имеет, как нам и нужно, тип слова, расположенного в стеке.
Для порождения этой локальнойпеременной мы отвели ей место в стеке с помощью командыsub sp,2; порождение локальной переменнойт.е. просто уменьшили значение регистра-указателя вершины стека на два байта. Эта переменная порождается, как мы и привыкли в языке Паскаль, с неопределённым начальным значением.
Этой жецели можно было бы достичь, например, более короткой (но менее понятной для читающего программу человека) командойpush ax; порождение локальной переменнойЗаметьте, что теперь переменная S порождается уже с начальным значением, равным значениюрегистра ax, что нам, вообще говоря, было не нужно.Перед возвратом из функции мы начали разрушение стекового кадра, как этого требуют стандартные соглашения о связях. Сначала командойadd sp,2; уничтожение локальной переменноймы уничтожили локальную переменную S (т.е. удалили её из стека), затем восстановили из стекастарые значения регистров cx, bx и bp (заметьте, что регистр bp нам больше не понадобится в нашей функции).
И, наконец, команда возвратаret 2*2; возврат с очисткой стекаудаляет из стека адрес возврата и значение двух слов – значений фактических параметров функции.Уничтожение стекового кадра завершено.Важной особенностью использования стандартных соглашений о связях является и то, что онипозволяют производить рекурсивный вызов процедур и функций, причём рекурсивный и не рекурсивный вызовы "по внешнему виду" не отличаются друг от друга. В качестве примера рассмотримреализацию функции вычисления факториала от неотрицательного (т.е.
беззнакового) целого числа,при этом будем предполагать, что значение факториала поместится в слово (иначе пусть мы выдадим неправильный результат без диагностики об ошибке). На языке Турбо-Паскаль эта функцияимеет следующий вид:Function Factorial(N: word): word;Beginif N<=1 then Factorial:=1else Factorial:=N*Factorial(N-1)End;40Будем надеяться, что язык Паскаль Вы все хорошо знаете, и без пояснений понимаете, как работает рекурсивная функция ☺. Реализуем теперь эту функцию в виде близкой процедуры на Ассемблере. Сначала заметим, что условный оператор Паскаляif N<=1 then Factorial:=1else Factorial:=N*Factorial(N-1)для программирования на Ассемблере неудобен, так как содержит две ветви (then и else), которые придётся размещать в линейной структуре машинной программы, что повлечёт использованиемежду этими ветвями команды безусловного перехода.
Поэтому лучше преобразовать этот операторв такой эквивалентный вид: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 на Ассемблере (со стандартными соглашениями о связях) необходимо решить следующие вопросы.