Newline
предназначена для перехода курсора к началу следующей строки экрана и эквивалентна вызову процедуры без параметров writeln языка Паскаль. Этого же эффекта можно достичь, если вывести на экран служебные символы с кодами 10 и 13, т.е. выполнить, например, макрокоманды
outch 10
outch 13
· Макрокоманда без параметров
Flush
предназначена для очистки буфера ввода и эквивалентна вызову процедуры без параметров readln языка Паскаль.
· Макрокоманда вывода на экран строки текста
Outstr
Эта макрокоманда выводит на экран строку текста из того сегмента, на который указывает сегментный регистр DS, причём адрес начала этой строки в сегменте должен находится в регистре DX. Таким образом, физический адрес начала выводимого текста определяется по формуле
Афиз = (DS*16 + DX)mod 220
Заданный таким образом адрес принято записывать в виде так называемой адресной пары <DS,DX>. В качестве признака конца выводимой строки символов должен быть задан символ $ (он рассматривается как служебный признак конца и сам не выводится). Например, если в сегменте данных есть текст
Data segment
. . .
T db ′Текст для вывода на экран$’
. . .
data ends
то для вывода этого текста на экран можно выполнить следующий фрагмент программы
. . .
mov DX,offset T; DX:=адрес T
Outstr
. . .
Рассмотрим теперь пример простой полной программы на Ассемблере. Эта программа должна вводить значение целой переменной A и реализовывать оператор присваивания (в смысле языка Паскаль)
X := (2*A - 241 div(A+B)2) mod 7
где B – параметр, т.е. значение, которое не вводится, а задаваётся в самой программе. Пусть A, B и С – знаковые целые величины, описанные в сегменте данных так:
A dw ?
B db –8; это параметр, заданный программистом
X dw ?
Вообще говоря, результат, заносимый в переменную X короткий (это остаток от деления на 7), однако мы выбрали для X формат слова, т.к. его надо выдавать в качестве результата, а макрокоманда outint может выводить только длинные целые числа.
Наша программа будет содержать три сегмента с именами data, code и stack и выглядеть следующим образом:
include io.asm
; вставить в программу файл с макроопределениями
; для макрокоманд ввода-вывода
data segment
A dw ?
B db -8
X dw ?
Data ends
stack segment stack
db 128 dup (?)
stack ends
code segment
assume cs:code, ds:data, ss:stack
start:mov ax,data; это команда формата r16,i16
mov ds,ax ; загрузка сегментного регистра DS
inint A ; макрокоманда ввода целого числа
mov bx,A ; bx := A
mov al,B ; al := B
cbw ; ax := длинное B
add ax,bx ; ax := B+A=A+B
add bx,bx ; bx := 2*A
imul ax ; (dx,ax) := (A+B)2
mov cx,ax ; cx := младшая часть(A+B)2
mov ax,241
cwd ; <dx,ax> := сверхдлинное 241
idivcx ; ax := 241 div (A+B)2 , dx := 241 mod (A+B)2
subbx,ax ; bx := 2*A - 241 div (A+B)2
movax,bx
Cwd
movbx,7
idivbx ; dx := (2*A - 241 div (A+B)2) mod 7
movX,dx
outint X
Finish
code ends
end start
Прокомментируем текст нашей программы. Во-первых, заметим, что сегмент стека мы нигде явно не используем, однако он необходим в любой программе. Как мы узнаем далее из нашего курса, во время выполнения любой программы возможно автоматическое (без нашего ведома) переключение на выполнение некоторой другой программы, при этом используется сегмент стека. Подробно этот вопрос мы рассмотрим при изучении прерываний.
В начале сегмента кода расположена директива assume, она говорит программе Ассемблера, на какие сегменты будут указывать соответствующие сегментные регистры при выполнении команд, обращающихся к этим сегментам. Сама эта директива не меняет значения ни одного сегментного регистра, подробно про неё необходимо прочитать в учебнике [5].
Заметим, что сегментные регистры SS и CS должны быть загружены перед выполнением самой первой команды нашей программы. Ясно, что сама наша программа этого сделать не в состоянии, так как для этого необходимо выполнить хотя бы одну команду, что требует доступа к сегменту кода, и, в свою очередь, уже установленного на этот сегмент регистра CS. Получается замкнутый круг, и единственным решением будет попросить какую-то другую программу загрузить значения этих регистров, перед вызовом нашей программы. Как мы потом увидим, эту операцию будет делать служебная программа, которая называется загрузчиком.
Первые две команды нашей программы загружают значение сегментного регистра DS, в младшей модели для этого необходимы именно две команды, так как одна команда имела бы несуществующий формат:
mov ds,data; формат SR,i16 такого формата нет!
Пусть, например, при счёте нашей программы сегмент данных будет располагаться, начиная с адреса 10000010 оперативной памяти. Тогда команда
mov ax,data
будет во время счёта иметь вид
mov ax,6250 ; 100000 div 16 = 6250
Макрокоманда
inint A; макрокоманда ввода целого числа
вводит значение целого числа в переменную A.
Далее начнём непосредственное вычисление правой части оператора присваивания. Задача усложняется тем, что величины A и B имеют разную длину и непосредственно складывать их нельзя. Приходится командами
mov al,B ; al := B
cbw ; ax := длинное B
преобразовать короткое целое B, которое сейчас находится на регистре al, в длинное целое на регистре ax. Далее вычисляется значение выражения (A+B)2 и можно приступать к выполнению деления. Так как делитель является длинным целым числом (мы поместили его на регистр cx), то необходимо применить операцию длинного деления, для чего делимое (число 241 на регистре ax) командой
Cwd
преобразуем в сверхдлинное целое и помещаем на два регистра (dx,ax). Вот теперь всё готово для команды целочисленного деления
idivcx; ax:= 241 div(A+B)2 , dx:= 241 mod(A+B)2
Далее мы присваиваем остаток от деления (он в регистре dx) переменной X и выводим значение этой переменной по макрокоманде
outint X
которая эквивалентна процедуре WriteLn(X) языка Паскаль. Последним предложением в сегменте кода является макрокоманда
Finish
Эта макрокоманда заканчивает выполнение нашей программы, она эквивалентна выходу программы на Паскале на конечный end.
И, наконец, директива
end start
заканчивает описание всего модуля на Ассемблере. Обратите внимание на параметр этой директивы – метку start. Она указывает входную точку программы, т.е. её первую выполняемую команду программы.
Сделаем теперь важные замечания к нашей программе. Во-первых, мы не проверяли, что команды сложения и вычитания дают правильный результат (для этого, как мы знаем, после выполнения этих команд нам было бы необходимо проверить флаг переполнения OF, т.к. наши числа мы считаем знаковыми). Во-вторых, команда длинного умножения располагает свой результат в двух регистрах (dx,ax), а в нашей программе мы брали результат произведения только из регистра ax, предполагая, что на регистре dx находятся только незначащие цифры произведения. По-хорошему надо было бы проверить, что в dx содержаться только нулевые биты, если ax ³ 0, и только двоичные “1”, если
ax < 0. Другими словами, знак числа в регистре dx должен совпадать со знаком числа в регистре ax, для знаковых чисел это и есть признак того, что в регистре dx содержится незначащая часть произведения. И, наконец, мы не проверили, что не производим деления на ноль (в нашем случае что A<>8). В наших учебных программах мы иногда не будем делать таких проверок, но в “настоящих” программах, которые Вы будете создавать на компьютерах и предъявлять преподавателям, эти проверки являются обязательными.
Продолжая знакомство с языком Ассемблера, решим следующую задачу. Напишем фрагмент программы, в котором увеличивается на единицу целое число, расположенное в 23456710 байте оперативной памяти. Мы уже знаем, что запись в любой байт памяти возможна только тогда, когда этот байт расположен в одном из четырёх текущих сегментах. Сделаем, например, так, чтобы наш байт располагался в сегменте данных. Главное здесь – не путать сегменты данных, которые мы описываем в программе на Ассемблере, с активными сегментами, на начала которых установлены сегментные регистры. Описываемые в программе сегменты обычно размещаются загрузчиком на свободных участках оперативной памяти, и, как правило, при написании текста программы неизвестно их будущего месторасположение.[14] Однако ничто не мешает нам любой участок оперативной памяти сделать сегментом, установив на него какой-либо сегментный регистр. Так мы и сделаем для решения нашей задачи, установив сегментный регистр DS на начало ближайшего сегмента, в котором будет находиться наш байт с адресом 23456710. Так как в сегментный регистр загружается адрес начала сегмента, делённый на 16, то нужное нам значение сегментного регистра можно вычислить по формуле: DS := 234567 div 16 = 14660. При этом адрес A нашего байта в сегменте (его смещение от начала сегмента) вычисляется по формуле: A := 234567 mod 16 = 7. Таким образом, для решения нашей задачи можно предложить следующий фрагмент программы:
mov ax,14660
mov ds,ax; Начало сегмента
mov bx,7; Смещение
inc byte ptr [bx]
Теперь, после изучения арифметических операций, перейдём к рассмотрению команд переходов, которые понадобятся нам для программирования условных операторов и циклов. После изучения нашего курса мы должны уметь отображать на Ассемблер любые конструкции языка Паскаль.
Дата добавления: 2015-10-05; просмотров: 895;