Студопедия

Главная страница Случайная страница

Разделы сайта

АвтомобилиАстрономияБиологияГеографияДом и садДругие языкиДругоеИнформатикаИсторияКультураЛитератураЛогикаМатематикаМедицинаМеталлургияМеханикаОбразованиеОхрана трудаПедагогикаПолитикаПравоПсихологияРелигияРиторикаСоциологияСпортСтроительствоТехнологияТуризмФизикаФилософияФинансыХимияЧерчениеЭкологияЭкономикаЭлектроника






Пример полной программы на Ассемблере






Прежде, чем написать нашу первую полную программу на Ассемблере, нам необходимо научиться выполнять операции ввода/вывода, без которых ни одна сколько-нибудь серьёзная программа обойтись не может. В самом языке машины, в отличие от языка нашей учебной машины УМ-3, нет команда ввода/вывода, [13] чтобы, например, ввести целое число, необходима достаточно большая программа на машинном языке.

Для организации ввода/вывода мы в наших примерах будем использовать макрокоманды из учебника [5]. Вместо каждой макрокоманды Ассемблер будет подставлять соответствующий этой макрокоманде набор команд и констант (этот набор, как мы узнаем позже, называется макрорасшире­нием для макрокоманды).

Нам понадобятся следующие макрокоманды ввода/вывода.

· Макрокоманда вывода символа на экран

outch op1

где операнд op1 может быть в формате i8, r8 или m8. Значение операнда трактуется как код символа, этот символ выводится в текущую позицию экрана. Для задания кода символа удобно использовать символьную константу языка Ассемблер, например, ′ A′. Такая константа преобразуется программой Ассемблера именно в код этого символа. Например, outch ′ *′ выведет символ звёздочки на место курсора.

· Макрокоманда ввода символа с клавиатуры

inch op1

где операнд op1 может быть в формате r8 или m8. Код введённого символа записывается в место памяти, определяемое операндом.

· Макрокоманды вывода на экран целого значения

outint op1[, op2]

outword op1[, op2]

Здесь, как всегда, квадратные скобки говорят о том, что второй операнд может быть опущен. В качестве первого операнда op1 можно использовать i16, r16 или m16, а второго – i8, r8 или m8. Действие макрокоманды outint op1, op2 полностью эквивалентно процедуре вывода языка Паскаль write(op1: op2), а действие макрокоманды с именем outword отличается только тем, что первый операнд трактуется как беззнаковое (неотрицательное) число.

· Макрокоманда ввода целого числа

inint op1

где операнд op1 может иметь формат r16 или m16, производит ввод с клавиатуры на место первого операнда целого значения из диапазона –2 15.. +2 16. Особо отметим, что операнды форматов r8 и m8 недопустимы.

· Макрокоманда без параметров

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

idiv cx; ax: = 241 div (A+B)2, dx: = 241 mod (A+B)2

sub bx, ax; bx: = 2*A - 241 div (A+B)2

mov ax, bx

Cwd

mov bx, 7

idiv bx; dx: = (2*A - 241 div (A+B)2) mod 7

mov X, 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). Вот теперь всё готово для команды целочисленного деления

idiv cx; 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]

Теперь, после изучения арифметических операций, перейдём к рассмотрению команд переходов, которые понадобятся нам для программирования условных операторов и циклов. После изучения нашего курса мы должны уметь отображать на Ассемблер любые конструкции языка Паскаль.






© 2023 :: MyLektsii.ru :: Мои Лекции
Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав.
Копирование текстов разрешено только с указанием индексируемой ссылки на источник.