Лекции Баулы (1110628), страница 9
Текст из файла (страница 9)
Итак, модуль состоит из описаний сегментов. В сегментах находятся все команды, и области памяти, используемые для хранения переменных. Вне сегментов могут располагаться только так называемые директивы языка Ассемблер, о которых мы будем говорить немного ниже. Пока лишь отметим, что чаще всего директивы не определяют в программе ни команд, ни переменных (поймите, что именно поэтому они и могут стоять вне сегментов).
Описание каждого сегмента, в свою очередь, состоит из предложений (statement) языка Ассемблера. Каждое предложение языка Ассемблера занимает отдельную строчку программы, исключение из этого правила будет отмечено особо. Далее рассмотрим различные классы предложений Ассемблера.
Классификация предложений языка Ассемблер
-
Многострочные комментарии. Это единственная конструкция Ассемблера, которая может занимать несколько строк текста программы. Будем для унификации терминов считать её неким частным типом предложения, хотя не все авторы учебников по Ассемблеру придерживаются этой точки зрения. Способ записи этих комментариев надо посмотреть в учебнике [5].
-
Команды. Почти каждому такому предложению языка Ассемблера будет соответствовать одна команде на языке машины (в редких случаях получаются две "тесно связанных" команды). Как уже отмечалось, вне описания сегмента такое предложение встречаться не может.
-
Резервирование памяти. Эти предложения отводят в том сегменте, где они записаны, области памяти для хранения переменных. Это некоторый аналог описания переменных языка Паскаль. Способ записи таких предложений надо посмотреть в учебнике [5], мы приведём лишь некоторые примеры с комментариями.
Предложение | Количество памяти |
A db ? | 1 байт |
B dw ? | 2 байта (слово) |
C dd ? | 4 байта (двойное слово) |
В этих примерах описаны переменные с именами A,B и C разной длины, которые, как мы уже привыкли в языке Паскаль, не будут иметь конкретных начальных значений, что отмечено символом вопросительного знака. Однако но принципу Фон Неймана ничто не мешает нам работать напрямую с одним или несколькими байтами, расположенными в любом месте памяти. Например, команда mov ax,B+1 будет читать на регистр ax слово, второй байт которого располагается в конце переменной B, а первый – в начале переменной C (помним о "перевёрнутом" хранении слов в памяти!). Поэтому следует быть осторожными и не считать A, B и C отдельными, "независимыми" переменными в смысле языка Паскаль.
Предложение
D dw 20 dup (?)
резервирует в сегменте 20 подряд расположенных слов с неопределёнными начальными значениями. Это можно назвать резервированием памяти под массив из 20 элементов, но при этом мы также не теряем возможности работать с произвольными байтами и словами из области памяти, зарезервированной под массив.
-
Директивы или команды Ассемблеру. Эти предложения, как уже упоминалось, не порождают в машинной программе никакого кода, т.е. команд или переменных (редким исключением является директива include, о которой мы будем говорить при написании полных программ). Директивы используются программистом для того, чтобы давать программе Ассемблер определённые указания, управлять его работой при компиляции (переводу) программы на язык машины. В качестве примера рассмотрим директивы объявления начала и конца описания сегмента с именем A:
A segment
...
A ends
Частный случай директивы является и предложение-метка, она приписывает имя (метку) следующему за ней предложению. Так, в приведённом ниже примере метка Next_Statement_Name является именем следующего за ней предложения, таким образом у этого предложения две метки:
Next_Statement_Name:
L: mov ax,2
-
Макрокоманды. Этот класс предложений Ассемблера относятся к разряду макросредств языка и будут подробно изучаться далее в нашем курсе. Пока лишь скажем, что на место макрокоманды по определённым правилам подставляется некоторый набор (возможно и пустой) предложений Ассемблера.
Теперь рассмотрим структуру одного предложения. За редким исключением, каждое предложение может содержать от одного до четырёх полей: поле метки, поле кода операции, поле операндов и поле комментария (как обычно, квадратные скобки указывают на необязательность заключённой в них конструкции):
[<метка>[:]] КОП [<операнды>] [; комментарий]
Как видно, все поля предложения, кроме кода операции, являются необязательными и могут отсутствовать в конкретном предложении. Операнды, если их в предложении несколько, отделяются друг от друга запятыми (в макрокоманде операнды могут разделяться и пробелами). Если после метки стоит двоеточие, то это указание на то, что данное предложение может рассматриваться как команда, т.е. выбираться для исполнения в устройство управления.
В очень редких случаях предложения языка Ассемблера имеют другую структуру, например, директива присваивания значения переменной периода генерации (с этими переменными мы познакомимся при изучении макросредств языка):
K = K+1
Другим примером может служить строка-комментарий (такие строки начинаются с символа точки с запятой, перед которой могут стоять только символы пробелов):
; это строка-комментарий
Пример программы на Ассемблере
Рассмотрим теперь пример простой полной программы на Ассемблере. Эта программа должна вводить значение целой переменной A и реализовывать оператор присваивания (в смысле языка Паскаль)
X := (2*A-4/(A+B)2) mod 7
где B – параметр, т.е. значение, задаваемое в самой программе. Пусть A, B и С – знаковые целые переменные, описанные в сегмента данных как:
A dw ?
B db –8; это параметр, здесь любое число
X dw ?
Наша программа будет содержать три сегмента и выглядеть следующим образом:
include io.asm
; файл с макроопределениями для макрокоманд ввода-вывода
data segment
A dw ?
B db ?
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 ; ax := (A+B)2
mov cx,ax ; cx := (A+B)2
mov ax,4
cwd ; (dx,ax) := сверхдлинное 4
idiv cx ; cx := 4/(A+B)2
sub bx,ax ; bx := 2*A-4/(A+B)2
mov ax,bx
cwd
mov bx,7
idiv bx ; dx := 2*A-4/(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 такого формата нет!
Макрокоманда
inint A ; макрокоманда ввода целого числа
вводит значение целого числа в переменную A, это аналог процедуры Readln(A) языка Паскаль. Напомним, что на место этой макрокоманды подставляется некоторый набор предложений Ассемблера, который и реализует такой ввод.
Далее начнём непосредственное вычисление правой части оператора присваивания. Задача усложняется тем, что величины A и B имеют разную длину и непосредственно складывать их нельзя. Приходится командами
mov al,B ; al := B
cbw ; ax := длинное B
преобразовывать короткое целое B к длинному целому на регистре AX. Далее вычисляется значение выражения (A+B)2 и можно приступать к выполнению деления. Так как делитель является длинным целым числом (мы поместили его на регистр cx), то необходимо применить операцию длинного деления, для чего делимое (число 4) командой
cwd
преобразуем в сверхдлинное целое и помещаем на два регистра (dx,ax). Вот теперь всё готово для команды целочисленного деления
idiv cx ; ax:= 4 div(A+B)2 , dx:= 4 mod(A+B)2
Далее мы присваиваем остаток от деления (он в регистре dx) переменной X и печатаем эту переменную по макрокоманде
outint X
которая эквивалентна процедуре WriteLn(X) языка Паскаль. Последним предложением в сегменте кода является макрокоманда
finish
которая заканчивает выполнение нашей программы, она эквивалентна выходу программы на Паскале на конечный end.
И, наконец, директива
end start
заканчивает описание всего модуля на Ассемблере. Обратите внимание на параметр этой директивы – метку start. Она указывает входную точку программы, т.е. первую выполняемую команду нашей программы.
Список литературы.
-
Г. Майерс. Архитектура современных ЭВМ (в 2-х книгах). – Мир, 1985.
-
Королёв Л.Н. Структуры ЭВМ и их математическое обеспечение. – Наука, 1985.
-
Любимский Э.З., Мартынюк В.В., Трифонов Н.П. Программирование. – Наука, 1980.
-
Пильщиков В.В. Программирование на языке Ассемблера IBM PC. – Диалог-МИФИ, 1994.
-
Скэлтон Л.Дж. Персональная ЭВМ IBM PC и XT. Программирование на языке Ассемблера. – Радио и связь, 1991.
-
Абель П. Язык Ассемлера для IBM PC и программирования.. – Высшая школа, 1992.
-
Нортон П., Соухэ Д. Язык Ассемблера IBM PC. – Компьютер, 1993.
-
Ю-Чжень Лю, Гибсон Г. Микропроцессоры семейства 8086/8088. – Радио и связь, 1987.
-
Донован Дж. Системное программрование. – Мир, 1975.
26