7_Язык Ассемблера (975804), страница 7
Текст из файла (страница 7)
Особым является случай, когда N=0, вэтом случае цикл выполнится 216 раз. К сожалению, никакой другой регистр нельзя использоватькак счётчик для организации этого цикла (т.к. это неявный параметр команды цикла). Кроме того, вприведённом выше примере реализации цикла тело этого цикла не может быть слишком большим,иначе команда loop L не сможет передать управление на метку L.1В качестве примера использования команды цикла решим следующую задачу. Требуется ввестибеззнаковое число N<=500, затем ввести N знаковых целых чисел и вывести сумму тех из них, которые принадлежат диапазону –2000..5000. На языке Турбо-Паскаль эта программа могла бы выглядеть следующим образом (мы заранее записываем её так, чтобы было легче перенести на Ассемблер):Const MaxN=500; S:integer=0;VarN: word; ax,cx: integer;BeginWrite('Введите N<=500 : '); Read(N);If N > MaxN then Writeln('Ошибка – большое N!') elseIf N > 0 then begin Write('Вводите целые числа');For cx:=N downto 1 do begin Read(ax);If (–2000<=ax) and (ax<=5000) then S:=S+axEndEnd;Writeln('S=',S)End.На Ассемблере можно предложить следующее решение этой задачи.include io.asm; файл с макроопределениями для макрокоманд ввода-выводаdata segmentMaxN equ500Ndw?Sdw0; Начальное значение суммы = 0T1db'Введите N<=500 $'T2db'Ошибка – большое N!$'T3db'Вводите целые числа',10,13,'$'T4db'Ошибка – большая сумма!$'data endsstack segment stackdw64 dup (?)stack endscode segmentassume cs:code,ds:data,ss:stackstart:movax,datamovds,axmovdx, offset T1; Приглашение к вводуoutstrinint N1В нашей архитектуре для эффективной реализации циклов применяются условные команды близкого перехода, как следствие тело цикла не может быть слишком большим (порядка 30-40 машинных команд), иначе,как уже говорилось, приходится пользоваться вспомогательными командами длинного безусловного перехода.Архитектурное решение о том, что циклы и условные переходы реализуются командами короткого перехода,опирается на так называемое свойство локальности программ.
Это свойство означает, что бóльшую часть времени центральный процессор выполняет команды, расположенные (локализованные) внутри относительно небольших участков программы (это и есть программные циклы), и достаточно редко переходит из одного такогоучастка в другой. Как мы узнаем позже, на свойстве локальности программ основано и существование в современных компьютерах специальной памяти типа кэш, а также так называемого расслоения оперативной памяти.21cmpN,MaxNjbeL1movdx,offset T2; Диагностика об ошибкеErr: outstrnewlinefinishL1:movcx,N; Счётчик циклаjcxz Pech; На печать результата при N=0movdx,offset T3; Приглашение к вводуoutstrL2:inint ax;Ввод очередного числаcmpax,-2000jlL3cmpax,5000jgL3;Проверка диапазонаaddS,ax; СуммированиеjnoL3;Проверка на переполнение Smovdx,offset T4jmpErrL3:loop L2Pech: outch 'S'outch '='outint Snewlinefinishcode endsend startКак видите, в отличие от Паскаля, наша программа на Ассемблере ещё и проверяет корректностьполученного результата.В качестве ещё одного примера рассмотрим использование циклов при обработке массивов.Пусть необходимо составить программу для решения следующей задачи.
Задана константаN=20000, надо ввести массивы X и Y по N беззнаковых чисел в каждом массиве и вычислить выражениеNS := ∑ X[i] * Y[N - i + 1]i =1Для простоты будем предполагать, что каждое из произведений и все частичные суммы всегдаимеют формат dw (т.е. помещаются в слово), иначе выдадим аварийную диагностику. Ниже приведена программа на Ассемблере, решающая эту задачу.include io.asmNequ20000; Аналог Const N=20000; Паскаляdata1 segmentT1db'Вводите числа массива $'T2db'Сумма = $'T3db'Ошибка – большое значение!',10,13,'$'Sdw0; искомая суммаXdwN dup (?); Массив X=2*N байтdata1 endsdata2 segmentYdwN dup (?); Массив Y=2*N байтdata2 endsstack segment stackdw64 dup(?)stack endscode segmentassume cs:code,ds:data1,es:date2,ss:stackbegin_of_my_program:movax,data122movds,ax;ds – на начало data1movax,data2moves,ax;es – на начало data2movdx,offset T1; Приглашение к вводуoutstroutch 'X'newlinemovcx,N; счётчик циклаmovbx,0; индекс массиваL1:inint X[bx];ввод очередного элемента X[i]addbx,2; увеличение индекса, это i:=i+1loop L1outstr; Приглашение к вводуoutch 'Y'newlinemovcx,N; счётчик циклаmovbx,0; индекс массиваL2:inint axmovY[bx],ax; ввод очередного элемента es:Y[bx]addbx,2; увеличение индексаloop L2movbx,offset X; указатель bx на X[1]movsi,offset Y+2*N-2; указатель si на Y[N]movcx,N; счётчик циклаL3:movax,[bx]; первый сомножительmulword ptr es:[si]; умножение на Y[N-i+1]jcErr; большое произведениеaddS,axjcErr; большая суммаaddbx,type X; это bx:=bx+2, т.е.
i:=i+1subsi,2; это i:=i-1loop L3; цикл суммированияmovdx,offset T2outstroutword SnewlinefinishErr: movdx,offset T3outstrfinishcode endsend begin_of_my_programПодробно прокомментируем эту программу. Количество элементов массивов мы задали, используя директиву эквивалентности N equ 20000 , это есть указание программе Ассемблера о том, чтовсюду далее в программе, где встретится имя N, надо подставить вместо него операнд этой директивы, т.е.
в нашем случае число 20000. Таким образом, это почти полный аналог описания константыв языке Паскаль.1 Так как длина каждого элемента массива равна двум байтом, то под каждый измассивов соответствующая директива dw зарезервирует 2*N байт памяти.Заметим теперь, что оба массива не поместятся в один сегмент данных (в сегменте не болеепримерно 32000 слов, а у нас в сумме 40000 слов), поэтому массив X мы размещаем в сегменте сименем data1, а массив Y – в сегменте с именем data2. Директива assume говорит, что во времясчёта на эти сегменты будут соответственно указывать регистры DS и ES, что мы и обеспечили, за1Если не принимать во внимание то, что константа в Паскале имеет тип, это позволяет контролировать еёиспользование в программе, а директива эквивалентности в Ассемблере – это просто указание о текстовойподстановке вместо имени заданного операнда директивы эквивалентности.
Иногда перед такой подстановкой производится также некоторое вычисление операнда директивы эквивалентности, подробнее об этом необходимо прочитать в учебнике [5].23грузив эти регистры нужными значениями в самом начале программы. При вводе массивов мы использовали индексный регистр bx, в котором находится смещение текущего элемента массива отначала этого массива.При вводе массива Y мы для учебных целей вместо предложенияL2: inint Y[bx]; ввод очередного элементазаписали два предложенияL2: inint axmov Y[bx],ax; ввод очередного элементаЭто мы сделали, чтобы подчеркнуть пользу от директивы assume: при доступе к элементам массиваY программа Ассемблера учитывает то, что имя Y описано в сегменте data2 и автоматически(используя информацию из директивы assume) поставит перед командой mov Y[bx],ax специальную однобайтную команду es: . Эту команду называют префиксом (замены) сегмента, такчто на языке машины у нас будут две последовательные, тесно связанные1 команды:es: mov Y[bx],axВ цикле суммирования произведений для доступа к элементам массивов мы использовали другой приём, чем при вводе – регистры-указатели bx и si, в этих регистрах находятся адреса очередных элементов массивов.
Напомним, что адрес – это смещение элемента относительно начала сегмента (в отличие от индекса элемента – это смещение от начала массива).При записи команды умножениеmul word ptr es:[si]; умножение на Y[N-i+1]мы вынуждены явно задать размер второго сомножителя и записать префикс сегмента es:, так какпо виду операнда [si] Ассемблер не может сам "догадаться", что это элемент массива Y размеромв слово и из сегмента data2 (директива assume здесь нам, к сожалению, помочь не сможет).В командеadd bx,type X; это bx:=bx+2для задания размера элемента массива мы использовали оператор type.
Параметром этого оператора является имя из нашей программы, значением оператора type <имя> является целое число –тип данного имени. Для имён областей памяти значение типа – это длина этой области в байтах (учтите, что для массива это почти всегда длина одного элемента, а не всего массива!), для меток команд это отрицательное число –1, если метка расположена в том же модуле, что и оператор type,или отрицательное число –2 для меток из других модулей.2 Все другие имена (в частности, именарегистров и сегментов), а также имена констант, имеют тип ноль.
В остальных случаях попытка использовать этот оператор без имени (например, type [bx] ) либо вызовет в нашем Ассемблересинтаксическую ошибку (например, в макрооператоре3 присваивания K=type [bx] – ошибка),либо оператор type будет проигнорирован (например, mov ax,type [bx] ≡ mov ax,[bx] ).Вы, наверное, уже почувствовали, что программирование на Ассемблере сильно отличается отпрограммирования на языке высокого уровня (например, на Паскале).
Чтобы подчеркнуть это различие, рассмотрим пример задачи, связанной с обработкой матрицы, и решим её на Паскале и на Ассемблере.Пусть дана матрица целых чисел и надо найти сумму элементов, которые расположены в строках, начинающихся с отрицательного значения. Для решения этой задачи на Паскале можно предложить следующий фрагмент программыConst N=20; M=30;VarX: array[1..N,1..M] of integer;Sum,i,j: integer;Begin{ Ввод матрицы X }Sum:=0;1Мы называем эту пару команд тесно связанными, потому что они одновременно выбираются для исполнения на регистр команд, и при счёте между ними никогда не происходит прерывания выполнении программы,подробнее об этом поговорим в разделе, посвящённом системе прерываний.2Мы уже упоминали, что программа на Ассемблере может состоять из отдельных модулей, подробно обэтом мы будем говорить в главе "Модульное программирование".3Об этом операторе мы будем говорить в отдельной главе, посвящённой макросредствам Ассемблера.24for i:=1 to N doif X[i,1]<0 thenfor j:=1 to M do Sum:=Sum+X[i,j];Для переноса программы с языка высокого уровня (в нашем случае с Паскаля) на язык низкогоуровня (Ассемблер) необходимо выполнить два преобразования, которые в программистской литературе часто называются отображениями.
Как говорят, надо отобразить с языка высокого уровняна язык низкого уровня структуру данных и структуру управления программы. В нашем примереглавная структура данных в программе на Паскале – это прямоугольная таблица (матрица) целых чисел. При отображении матрица на линейную память некоторого сегмента данных в Ассемблере приходится, как говорят, линеаризировать матрицу.