Chapter_07 (1110559), страница 11
Текст из файла (страница 11)
Другими словами то, что это функция, а не процедура, знает программист, но не программа Ассемблера,поэтому далее мы часто будем использовать для этой цели обобщённый термин процедура.Перед тем, как писать процедуру, необходимо составить соглашение о связях между основнойпрограммой и процедурой. Здесь необходимо уточнить, что под основной программой мы имеем ввиду то место нашей программы, где процедура вызывается по команде call. Таким образом, вполне возможен и случай, когда одна процедура вызывает другую, в том числе и саму себя, используяпрямую или косвенную рекурсию (напомним, что при прямой рекурсии процедура вызывает себясама непосредственно, а при косвенной – через цепочку других процедур).Соглашение о связях между основной программой и процедурой включает в себя место расположения и способ передачи параметров, способ возврата результата работы (для функции) и некоторую другую информацию.
Так, для нашего последнего примера мы "договоримся" с процедурой, чтосуммируемый массив слов будет располагаться в сегменте данных (на него указывает регистр DS),адрес (не индекс!) начала массива (его первого элемента) перед вызовом процедуры будет записан врегистр bx, а количество элементов – в регистр cx. Сумма элементов массива при возврате из процедуры должна находится в регистре ax. При этих соглашениях о связях у нас получится следующаяпрограмма (для простоты вместо команд для ввода массивов вы указали только комментарий).include io.asmdata segmentXdw100 dup(?)Ydw200 dup(?)Sumdw?data endsstack segment stackdw64 dup (?)stack endscode segment34assume cs:code,ds:data,ss:stackSumma proc near; соглашение о связях: bx – адрес первого элемента; cx=количество элементов, ax – ответ (сумма)subax,ax; сумма:=0L:addax,[bx]addbx,2; на следующий элементloop LretSumma endpstart:movax,datamovds,ax; здесь команды для ввода массивов X и Уmovbx, offset X; адрес начала Xmovcx,100; число элементов в Xcall SummamovSum,ax; сумма массива Xmovbx, offset Y; адрес начала Ymovcx,200; число элементов в Ycall SummaaddSum,ax; сумма массивов X и Youtint Sumnewlinefinishcode endsend startНадеюсь, что Вы легко разберитесь, как работает эта программа.
А вот теперь, если попытаться"один к одному" переписать эту Ассемблерную программу на язык Турбо-Паскаль, то получитсяпримерно следующее:Program S(input,output);VarX: array[1..100] of integer;Y: array[1..200] of integer;bx: ↑integer; Sum,cx,ax: integer;Procedure Summa;Label L;Beginax:=0;L: ax := ax + bx↑;bx:=bx+2; {так в Паскале нельзя}dec(cx);if cx<>0 then goto LEnd;Begin {Ввод массивов X и Y}cx:=100; bx:=↑X[1]; {так в Паскале нельзя}1Summa; Sum:=ax;cx:=200; bx:=↑Y[1]; {так в Паскале нельзя}Summa; Sum:=Sum+ax; Writeln(Sum)End.Как видим, у этой программы очень плохой стиль программирования, так как неявными параметрами процедуры являются глобальные переменные, т.е.
полезный механизм передачи параметровПаскаля просто не используется. В то же время именно хорошо продуманный аппарат формальных ифактических параметров делает процедуры и функции таким гибким, эффективным и надёжным механизмом в языках программирования высокого уровня. Если с самого начала решать задачу сумми1В языке Турбо-Паскаль для этой цели можно использовать оператор присваивания bx:=@X[1]35рования массивов на Паскале, а не на Ассемблере, мы бы, конечно, написали, например, такую программу:Program A(input,output);Type Mas= array[1..N] of integer;{так в Паскале нельзя, N – не константа}VarX,Y: Mas;Sum: integer;Function Summa(Var A: Mas, N: integer): integer;Var i,S: integer;Begin S:=0;for i:= 1 to N do S:=S+A[i];Summa:=SEnd;Begin {Ввод массивов X и Y}Sum:=Summa(X,100)+Summa(Y,200); Writeln(Sum)End.Это уже достаточно грамотно составленная программа на Паскале.
Теперь нам надо научитьсятак же хорошо писать программы с процедурами и на Ассемблере, однако для этого нам понадобятсядругие, несколько более сложные, соглашения о связях между процедурой и основной программой.Если Вы хорошо изучили программирование на Паскале, то должны знать, что грамотно написаннаяпроцедура в этом языке получает все свои входные данные как фактические параметры и не использует внутри себя глобальных имён переменных и констант.
Такого же стиля работы с процедурамимы должны, по возможности, придерживаться и при программировании на Ассемблере. При работе спроцедурами на языке Ассемблера мы будем использовать так называемые стандартные соглашения о связях.7.10.1. Стандартные соглашения о связяхСначала поймём необходимость существования некоторых стандартных соглашений о связяхмежду процедурой и вызывающей её основной программой.
Действительно, иногда программистпросто не сможет "договориться", например, с процедурой, как она должна принимать свои параметры. В качестве первого примера можно привести так называемые библиотеки стандартных процедур. В этих библиотеках собраны процедуры, реализующие алгоритмы для некоторой предметнойобласти (например, для работы с матрицами), так что программист может внутри своей программывызывать нужные ему процедуры из такой библиотеки. Библиотеки стандартных процедур обычнопоставляются в виде набора так называемых объектных модулей, что исключает для пользователявозможность вносить какие-либо изменения в исходный текст этих процедур (с объектными модулями мы познакомимся далее в нашем курсе).
Таким образом, получается, что взаимодействовать спроцедурами из такой библиотеки можно, только используя те соглашения о связях, которые былииспользованы при создании этой библиотеки.Другим примером является написание частей программы на различных языках программирования, при этом чаще всего основная программа пишется на некотором языке высокого уровня (Фортране, Паскале, С и т.д.), а процедура – на Ассемблере. Вспомним, что когда мы говорили об областях применения Ассемблера, то одной из таких областей и было написание процедур, которые вызываются из программ на языках высокого уровня. Например, для языка Турбо-Паскаль такая, как говорят, внешняя, функция может быть описана в программе на Паскале следующим образом:Function Summa(Var A: Mas, N: integer): integer; External;Служебное слово External является указанием на то, что эта функция описана не в даннойпрограмме и Паскаль-машина должна вызвать эту внешнюю функцию, как-то передать ей параметры и получить результат работы функции.
Если программист пишет эту функцию на Ассемблере, тоон конечно никак не может "договориться" с компилятором, как он хочет получать параметры и возвращать результат работы своей функции.Именно для таких случаев и разработаны стандартные соглашения о связях. При этом, еслипроцедура или функция, написанная на Ассемблере, соблюдает эти стандартные соглашения о связях, то это гарантирует, что эту процедуру или функцию можно будет вызывать из программы, напи-36санной на другом языке программирования, если в этом языке (более точно – в системе программирования, включающей в себя этот язык) тоже соблюдаются такие же стандартные соглашения освязях.Рассмотрим типичные стандартные соглашения о связях между основной программой и процедурой, эти соглашения поддерживаются многими современными системами программирования.Обычно стандартные соглашения о связях включают в себя следующие пункты.• Фактические параметры перед вызовом процедуры или функции записываются в стек.1 Какмы помним, в языке Паскаль параметры можно передавать в процедуру по значению и поссылке, это верно и для многих других языков программирования.2 Так вот, при передачепараметра по значению в стек записывается само это значение, а в случае передачи параметра по ссылке в стек записывается адрес начала фактического параметра.3 Порядок записи фактических параметров в стек может быть прямым (сначала записывается первый параметр, потом второй и т.д.) или обратным (когда, наоборот, сначала записывается последний параметр, потом предпоследний и т.д.).
В разных языках программирования этотпорядок различный. Так, в языке С по умолчанию это обратный порядок, а в большинстведругих языков программирования высокого уровня – прямой.4• Если в процедуре или функции необходимы локальные переменные (в смысле языка Паскаль), то место им отводится в стеке. Обычно это делается путём записи в стек некоторыхвеличин, или же увеличением значения указателя вершины стека, для чего в нашей архитектуре, как мы уже знаем, надо уменьшить значение регистра SP на число байт, которые занимают эти локальные переменные.• Функция возвращает своё значение в регистрах AL, AX или в паре регистров <DX,AX>, в зависимости от величины этого значения. Для возврата значений, превышающих двойное слово, устанавливаются специальные соглашения.5• Если в процедуре или функции изменяются какие-либо регистры, то в начале работы необходимо запомнить значения этих регистров в локальных переменных (т.е. в стеке), а передвозвратом – восстановить эти значения.