7_Язык Ассемблера (975804), страница 3
Текст из файла (страница 3)
Пример полной программы на АссемблереПрежде, чем написать нашу первую полную программу на Ассемблере, нам необходимо научиться выполнять операции ввода/вывода, без которых, естественно, ни одна сколько-нибудь серьёзная программа обойтись не может. В самом языке машины, в отличие от, например, языка нашейучебной машины УМ-3, нет команд ввода/вывода,1 поэтому для того, чтобы, например, ввести целоечисло, необходимо выполнить некоторый достаточно сложный фрагмент программы на машинномязыке.Для организации ввода/вывода мы в наших примерах будем использовать набор макрокомандиз учебника [5]. Вместо каждой макрокоманды Ассемблер будет при компиляции подставлять соответствующий этой макрокоманде набор предложений языка Ассемблер (этот набор, как мы узнаемпозже, называется макрорасширением для данной макрокоманды).
Таким образом, на первом этапеизучения языка Ассемблера, мы избавляемся от трудностей, связанных с организацией ввода/вывода, используя для этого заранее заготовленные для нас фрагменты программ.Нам понадобятся следующие макрокоманды ввода/вывода.• Макрокоманда ввода символа с клавиатуры 2inch op1где операнд op1 может иметь формат r8 или m8. Код введённого символа записывается в местопамяти, определяемое операндом. Эта макрокоманда эквивалентна оператору Паскаля для ввода одного символа Read(op1).• Макрокоманда вывода символа на экранoutch op1где операнд op1 может иметь формат i8, r8 или m8. Значение операнда трактуется как беззнаковое число, являющееся кодом (номером) символа, этот символ выводится в текущую позицию экрана. Для задания кода символа удобно использовать символьную константу языка Ассемблер, например, 'A', тогда можно не задаваться вопросом о кодировке символов, т.е.
соответствие самихсимволов и их номеров в используемом алфавите. Такая константа преобразуется программой Ассемблера именно в код этого символа, т.е. конструкция 'A' полностью эквивалентна записиord('A') языка Паскаль. Например, макрокоманда outch '*' выведет символ звёздочки на место курсора. Другими словами, наша макрокоманда outch эквивалентна оператору Паскаля для вывода одного символа Write(op1).• Макрокоманды вывода на экран целого значенияoutint op1[,op2]outword op1[,op2]1Точнее, в машинном языке есть только команды для обмена одним байтом или одним словом между регистром центрального процессора и заданным в команде особым периферийным устройством компьютера(портом ввода/вывода), с этими командами мы познакомимся позже.2Как Вы должны помнить из курса языка Паскаль, на самом деле ввод производится из стандартноговходного потока input, а вывод – в стандартный выходной поток output.
Чаще всего поток input подключён к клавиатуре, а output – к экрану терминала. Эти же соглашения действуют и в Ассемблере, хотя за этимипотоками и не закреплены имена input и output.7Здесь, как всегда при описании синтаксиса, квадратные скобки говорят о том, что второй операнд может быть опущен. В качестве первого операнда op1 можно использовать форматы i16, r16или m16, а второго – i8, r8 или m8. Действие макрокоманды outint op1,op2 эквивалентновыполнению процедуры вывода одного целого значения языка Паскаль write(op1:op2), где второй параметр может задавать ширину поля вывода. Действие же макрокоманды с именем outwordотличается только тем, что первый операнд при выводе трактуется как беззнаковое (неотрицательное) целое число.
Заметим, что в данной макрокоманде на месте операнда op1 нельзя использовать данные форматов r8 и m8.• Макрокоманда ввода целого числаinint op1где операнд op1 может иметь формат r16 или m16, производит ввод с клавиатуры на местопервого операнда любого целого значения из диапазона –215..+216 (этот диапазон является объединением диапазонов знаковых и беззнаковых чисел). Особо отметим, что операнды форматов r8 иm8 в этой макрокоманде недопустимы.• Макрокоманда без параметровnewlineпредназначена для перевода курсора к началу следующей строки экрана и эквивалентна вызовустандартной процедуры без параметров Writeln языка Паскаль. Этого же эффекта можно такжедостичь, если последовательно вывести на экран служебные символы с десятичными кодами 10 и13, т.е. выполнить, например, две макрокомандыoutch 10outch 13• Макрокоманда без параметровflushпредназначена для очистки буфера ввода и эквивалентна вызову стандартной процедуры без параметров Readln языка Паскаль.• Макрокоманда вывода на экран строки текстаoutstrЭта макрокоманда не имеет явных операндов, она всегда выводит на экран строку текста из тогосегмента, на который указывает сегментный регистр DS, причём адрес начала выводимой строки всегменте должен находится в регистре DX.
Таким образом, физический адрес начала выводимоготекста определяется по формулеАфиз = (DS*16 + <DX>)mod 220Заданный таким образом адрес принято записывать в виде уже знакомой нам адресной пары<DS,DX>. В качестве признака конца выводимой строки символов должен быть задан символ $, онрассматривается как служебный символ и сам не выводится.
Например, если в сегменте данных естьтекстdata segment. . .Tdb 'Текст для вывода на экран$'. . .data endsто для вывода этого текста на экран можно выполнить следующий фрагмент программы. . .mov DX,offset T; DX:=адрес Toutstr.
. .Здесь на место второго операнда команды mov DX,offset T Ассемблер подставит адрес(т.е. смещение – offset) начала текста T в сегмента данных. Этого же результата можно достичь ис помощью команды lea DX,T , которая заносит на свой первый операнд-регистр DX адрес второго операнда (а это и будет смещение текста T от начала сегмента данных).8Рассмотрим теперь пример простой полной программы на Ассемблере. Эта программа должнавводить значение целой переменной A и реализовывать оператор присваивания (в смысле языкаПаскаль)X := (2*A - 1234 div (A+B)2) mod 7Пусть A, B и X – знаковые целые величины, описанные в сегменте данных таким образом:A dw ?B db –8; это параметр, заданный программистомX dw ?По этим предложениям Ассемблер зарезервирует в памяти поля для хранения переменных A,B иX, причём переменная B длиной в один байт будет иметь начальное значение -8.
Будем считатьзначение B параметром нашей задачи, т.е. величиной, которая не вводится в программу, а задаётсяпрограммистом в виде константы (параметр, в отличие от "настоящей" константы, иногда, хотя иредко, может меняться, например, перед запуском задачи на счёт с другими входными данными).В переменной X у нас будет получаться результат работы. Вообще говоря, результат, заносимыйв переменную X – короткое целое число (это остаток от деления на 7, который помещается в одинбайт).
Мы, однако, выбрали для хранения значения X формат слова, т.к. его надо будет выдавать вкачестве результата, а наша макрокоманда outint может выводить, как уже говорилось, толькодлинные целые числа.Наша программа будет содержать три сегмента с именами data, code и stack и выглядетьследующим образом (подробные пояснения мы дадим сразу же вслед за текстом программы):include io.asm; директива include вставляет в программу файл с; макроопределениями для макрокоманд ввода-выводаdata segmentAdw ?Bdb -8Xdw ?data endsstack segment stackdb 128 dup (?)stack endscode segmentassume cs:code, ds:data, ss:stackstart :mov ax,data; это команда формата r16,i16movds,ax ; загрузка сегментного регистра DSinint A; макрокоманда ввода целого числаmovbx,A ; bx := Amoval,B ; al := Bcbw; ax := длинное Baddax,bx ; ax := B+A=A+Baddbx,bx ; bx := 2*Aimul ax; <dx,ax> := (A+B)22; Пусть (A+B) помещается в одно слово!movcx,ax ; cx := младшая часть(A+B)2movax,1234cwd; <dx,ax> := сверхдлинное 1234idiv cx; ax := 1234 div (A+B)2; dx := 1234 mod (A+B)2subbx,ax ; bx := 2*A - 1234 div (A+B)2movax,bxcwdmovbx,7idiv bx; dx := (2*A - 1234 div (A+B)2) mod 7movX,dxoutint Xfinish9code endsend startПодробно прокомментируем текст нашей программы.
Во-первых, заметим, что сегмент стека сименем stack мы нигде явно не используем, однако он необходим в любой программе. Как мы узнаем далее из нашего курса, во время выполнения каждой программы возможно автоматическое, безнашего ведома, переключение на выполнение некоторой другой программы. При таком переключении обязательно производится запись определённых данных в сегмент стека, поэтому Ассемблертребует, чтобы такой сегмент был в любой полной программе. Подробно этот вопрос мы рассмотримдалее в нашем курсе при изучении системы прерываний.В начале сегмента кода расположена директива assume, она говорит программе Ассемблера, накакие сегменты будут указывать соответствующие сегментные регистры при выполнении команд,обращающихся к этим сегментам. Этой директивой программист уведомляет программу Ассемблеро том, что он (программист) гарантирует следующее: во время счёта программы сегментные регистры будут указывать на описанные в программе сегменты, как это показано на рис.
7.2 (хотя порядоксегментов в памяти и не обязан быть именно таким). Заметьте, что сама директива assume не меняет (не устанавливает) значение ни одного сегментного регистра, подробно про неё необходимо обязательно прочитать в учебнике [5].SSStack segment stackDSData segmentCSCode segmentРис. 7.2. Требуемые значения сегментных регистровво время счёта нашей программы.Заметим, что сегментные регистры SS и CS должны быть загружены перед выполнением самойпервой команды нашей программы. Ясно, что сама наша программа этого сделать не в состоянии,так как для этого необходимо выполнить хотя бы одну команду, что требует доступа к сегменту кода, и, в свою очередь, уже установленного на этот сегмент регистра CS. Получается замкнутый круг,и единственным решением будет попросить какую-то другую программу загрузить значения этихрегистров, перед началом счёта нашей программы. Как мы потом увидим, это будет делать специальная служебная программа, которая называется загрузчиком, и нам, таким образом, об этом нестоит беспокоиться.1Первые две команды нашей программы загружают значение сегментного регистра DS, иначе невозможно работать с данными их этого сегмента.
В младшей модели для этого необходимы именнодве команды, так как одна команда, которую часто пишут нерадивые учащиеся, имела бы несуществующий формат:mov ds,data; формат SR,i16 такого формата нет!Пусть, например, при счёте нашей программы сегмент данных будет располагаться, начиная садреса 10000010 оперативной памяти. Тогда команда1Точнее, в тексте программы нам необходимо только как-то задать значения, которые надо загрузить в этирегистры, что для этого надо сделать мы скоро узнаем, а затем ещё раз строго определим это при описании работы загрузчика.10mov ax,dataбудет во время счёта иметь видmov ax,6250 ; 6250=100000 div 16; формат r16,i16Эта команда заносит на сегментный регистр данных адрес начала сегмента с именем data, делённый на 16, только после этого мы можем читать и писать данные в этот сегмент.Следующим предложением нашей программы является макрокомандаinint A; макрокоманда ввода целого числакоторая вводит значение целого числа в переменную A.Далее начнём непосредственное вычисление правой части оператора присваивания.