Chapter_07 (1110559), страница 3
Текст из файла (страница 3)
За редким исключением, каждое предложение может содержать от одного до четырёх полей: поле метки, поле кода операции, поле операндов и поле комментария (как обычно, квадратные скобки в описании синтаксиса указывают на необязательность заключённой в них конструкции):[<метка>[:]] КОП [<операнды>] [; комментарий]Как видно, все поля предложения, кроме кода операции, являются необязательными и могут отсутствовать в конкретном предложении. Метка является именем предложения, как и в Паскале, имяобязано начинаться с буквы, за которой следуют только буквы и цифры. В отличие от Турбо Паскаля, однако, в языке Ассемблера кроме знака подчёркивания к буквам относятся также символы'$','@','?' и даже точка (правда, только в первой позиции имени).
Как и в Паскале, длина имениограничена максимальной длиной строки предложения Ассемблера. Если после метки стоит двоеточие, то обычно это указание на то, что данное предложение может рассматриваться как команда, т.е.на него Ассемблеру можно, как говорят, передавать управление.11Как мы узнаем при изучении команд переходов, наличие у метки двоеточия для Ассемблера есть признактого, что надо использовать прямой, а не косвенный тип перехода по этой метке.6Операнды, если их в предложении несколько, отделяются друг от друга запятыми (замечание набудущее: в макрокоманде операнды могут разделяться не только запятыми, но и пробелами).
Каждый операнд является, как говорят, адресным выражением, в простейших случаях это имена меток ипеременных, подробно про адресные выражения необходимо прочитать в учебнике [5].В очень редких случаях предложения языка Ассемблера имеют другую структуру, например,директива присваивания значения переменной периода генерации (с этими переменными мы познакомимся при изучении макросредств языка):K = K+1Другим примером предложения, имеющего нестандартную структуру, может служить строкакомментарий, такие строки начинаются с символа точки с запятой, перед которой могут стоять только символы пробелов:; это строка-комментарий7.5.
Пример полной программы на АссемблереПрежде, чем написать нашу первую полную программу на Ассемблере, нам необходимо научиться выполнять операции ввода/вывода, без которых, естественно, ни одна сколько-нибудь серьёзная программа обойтись не может. В самом языке машины, в отличие от, например, языка нашейучебной машины УМ-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, она говорит программе Ассемблера, накакие сегменты будут указывать соответствующие сегментные регистры при выполнении команд,обращающихся к этим сегментам.