Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Ответы на госы бакалавриат комета.doc
Скачиваний:
404
Добавлен:
22.09.2018
Размер:
6.32 Mб
Скачать

10. Ассемблер. Основные языковые конструкции. Необходимость двухпроходной трансляции. Основные работы, выполняемые транслятором. Таблицы транслятора.

Ассемблер (от. англ. сборщик) — транслятор исходного текста программы, написанной на языке ассемблера, в программу на машинном языке.

Как и сам язык, ассемблеры, как правило, специфичны для конкретной архитектуры, операционной системы и варианта синтаксиса языка. Вместе с тем существуют мульти платформенные или вовсе универсальные (точнее, ограниченно-универсальные, потому что на языке низкого уровня нельзя написать аппаратно-независимые программы) ассемблеры, которые могут работать на разных платформах и операционных системах. Среди последних можно также выделить группу кросс-ассемблеров, способных собирать машинный код и исполняемые модули (файлы) для других архитектур и операционных систем.

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

Алфавит Ассемблера состоит из прописных и строчных латинских букв, из цифр, знаков препинания и некоторых других символов, которые перечислены ниже в порядке возрастания их ASCII кода:

! # “ $ % & ` ( ) * + , - . /

0 1 2 3 4 5 6 7 8 9 : ; < = > ?

@ A B C D E F G H I J K L M N O

P Q R S T U V W X Y Z [ \ ] ^ _

' a b c d e f g h i j k l m n o

p q r s t u v w x y z { | } ~

Кроме того, транслятор воспринимает символы, не имеющие отображения (пробел, табуляцию и пару символов - возврат каретки и перевод строки), но изменяющих положение следующих за ними символов текста. Транс­лятор производит грамматический разбор текста построчно. Каждая строка (аналог предложения) делится на лексемы (аналоги слов). В Ассемб­лере имеются лексемы 5 видов: специальные символы, имена (идентифи­каторы), числовые литералы, строковые литералы и комментарии. Специ­альные символы служат для обозначения операций в выражениях и в качестве разделителей других лексем.

Типичными командами языка ассемблера являются:

· Команды пересылки данных (mov и др.)

· Арифметические команды (add, sub, imul и др.)

· Логические и побитовые операции (or, and, xor и др.)

· Команды управления ходом выполнения программы (jmp, loop и др.)

· Команды вызова прерываний (иногда относят к командам управления): int

· Команды ввода-вывода в порты (in, out)

· Для микроконтроллеров и микрокомпьютеров характерны также команды, выполняющие проверку и переход по условию, например:

· cjne— перейти, если не равно

· sjnz — декрементировать, и если результат ненулевой, то перейти

· cfsneq— сравнить, и если не равно, пропустить следующую команду

Ассемблер не является языком свободной формы. Синтаксис Ассемб­лера в части записи операторов следует таким же правилам, какие приме­нялись для программирования в кодах. Каждый оператор Ассемблера также занимает одну строку, состоящую из четырех полей:

поле_метки, поле_мнемокода, поле_операндов, поле комментариев.

В качестве разделителей между полями можно использовать пробелы (не меньше одного) или знаки табуляции. В качестве разделителей между операндами (если их два или больше) используется запятая. Поле метки заполняется только в том случае, если в программе нужно сделать ссылку на данную строку. Поле мнемокода должно быть заполнено обязательно, так как в нем записывается производимое оператором действие. Необхо­димость записи в поле операндов зависит от мнемокода. Поле коммента­риев транслятору не нужно, поэтому остаток строки после точки с запя­той не обрабатывается. Эта часть исходного текста существенна только для программиста. Строка должна заканчиваться парой символов - возврата каретки и перевода строки.

Итак, как известно, Ассемблер относится к классу системных программ, которые называются трансляторами - переводчиками с одного алгоритмического языка на другой. Наш транслятор явля­ется компилятором, он переводит модуль с языка Ассемблера на объектный язык. При трансляции выполняются следующие шаги.

  • Анализ входного модуля на наличие синтаксических ошибок.

  • Выдача протокола работы компилятора - так называемого листинга, а также выдачу ава­рийных сообщений об ошибках. Выдачу листинга при желании, как правило, можно забло­кировать.

  • Генерация объектного модуля.

Сначала будет разобран первый этап - анализ программы на наличие ошибок. Ясно, что найти все ошибки можно, только просмотрев весь текст модуля, строка за строкой. Каждый просмотр тек­ста программного модуля компилятором называется проходом. Компилятор с Ассемблера просмат­ривает программу дважды, т.е. совершает два прохода. Такие компиляторы называются двухпроход­ными. Двухпроходная схема трансляции наиболее простая для реализации, но иногда можно, однако, усложнив алгоритм, производить трансляцию и за один проход (например, так работает транслятор с языка Турбо-Паскаль). В основном это можно сделать потому, что в Паскале практически всегда имя пользователя описывается или объявляется перед его первым использованием в программе.

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

Сначала каждое предложение Ассемблера обрабатывается специальной программой транслятора, которая называется лексическим анализатором. Она разбивает каждое предложение на лексемы - логически неделимые единицы языка. О лексемах Вы уже должны немного знать из изучения языка Паскаль. Как и в Паскале, в языке Ассемблера существуют следующие классы лексем.

  • Имена. Это ограниченные последовательности букв и цифр, начинающиеся с буквы, при этом большие и маленькие буквы не различаются. Как Вы помните, в Турбо-Паскале к бук­вам относился также и символ подчёркивания, а в Ассемблере к буквам относятся и такие символы, как вопросительный знак, точка (правда, только в первой позиции и у служебных имен) и некоторые другие символы. Все имена Ассемблера делятся на служебные и имена пользователя. В отличие от Паскаля, в Ассемблере нет стандартных имен (напомним, что стандартное имя имело заранее определённый смысл, но эти имена в Паскале программисту можно было переопределить).

  • Числа. Язык Ассемблера, как и Турбо-Паскаль, допускает запись целых чисел в различных системах счисления (десятичной, двоичной, шестнадцатеричной и некоторых других). Не надо забывать и о вещественных числах.

  • Строки символов. Строки символов в Ассемблере ограничены либо апострофами, либо двойными кавычками. Исключением является строка-параметр директивы эквивалентности, например, [ X equ ABC++ ]. Напомним, что выполнение этой директивы требует замены во всех следующих предложениях имени X на строку символов ABC++. После такой замены обычно требуется повторное разбиение строки на лексемы. Кроме того, надо ещё раз на­помнить, что в этом упрощенном изложении алгоритма работы компилятора считается, что Макропроцессор, который рассматривал фактические параметры макрокоманд тоже как строки символов, ограниченных запятыми, пробелами или точкой с запятой, уже закончил свою работу.

  • Разделители. Как и в Паскале, лексемы перечисленных выше классов не могут располагать­ся в тексте программы подряд, легко понять, что между ними обязательно должна нахо­диться хотя бы одна лексема-разделитель. К разделителям относятся знаки арифметических операций, почти все знаки препинания, пробел и некоторые другие символы.

  • Комментарии. Эти лексемы не влияют на выполнение алгоритма, заложенного в программу, они переносятся в листинг, а из анализируемого текста удаляются. Не являются исключени­ем макрокомментарии, которые начинаются с двух символов , такие комментарии не переносятся в макрорасширения и попадают в листинг программы в одном экземпляре (т.е. только внутри текста макроопределений).

В качестве примера ниже показано предложение Ассемблера,

каждая лексема в нем выделена в прямоугольник, обратите внимание на лексему-пробел между ко­дом операции mov и первым операндом ax:

На этапе лексического анализа выявляются лексические ошибки, когда некоторая группа сим­волов не может быть отнесена ни к одному из классов лексем. Примеры лексических ошибок:

134X 'A'38 B”C

Все опознанные (правильные) лексемы записываются на первом проходе в специальную табли­цу лексем. В этой таблице вместе с каждой лексемой указывается её класс и некоторые другие атри­буты. Это делается в основном для того, чтобы на втором проходе транслятор мог уже двигаться по программе по лексемам, а не по отдельным составляющим их символам, что позволяет существенно ускорить второй просмотр текста модуля.

После разбиения предложения Ассемблера на лексемы начинается второй этап обработки пред­ложения - этап синтаксического анализа. Этот этап выполняет программа транслятора, которая называется синтаксическим анализатором. Алгоритмы синтаксического анализа могут быть весь­ма сложны, и здесь они не будут подробно рассматриваться, это, как уже упоминалось, отдельная те­ма. Если говорить совсем коротко, то синтаксический анализатор пытается из лексем построить более сложные конструкции предложения: поля метки, кода операции и операндов. Особое внимание на этом этапе уделяется в программе именам пользователя, они заносятся синтаксическим анализатором в специальную таблицу имен (часто её ещё называют таблицей символических имён или просто таблицей символов). Вместе с каждым именем в эту таблицу заносятся и атрибуты (свойства) име­ни. Всего в Ассемблере у имени пользователя различают четыре основных атрибута, перечисленные ниже (сразу надо отметить, что не у каждого имени есть все из них). Для удобства дальнейшего из­ложения этим атрибутам присвоены имена Segment, Offset, Type и Value.

· Атрибут Segment. Этот атрибут имеет формат i16, он задаёт адрес начала (делённый на 16) того сегмента, в котором описано или объявлено данное имя.

· Атрибут Offset, он также имеет формат i16 и задаёт смещение расположения имени от начала того сегмента, в котором оно описано. Атрибуты Segment и Offset могут иметь только имена, определяющие области памяти и метки команд.

  • Атрибут типа Type. С этим атрибутом имени Вы уже знакомы, для имен переменных он равен длине переменной в байтах, а для меток равен near=-1 и far=-2 для близкой и дальней (в другом модуле) метки соответственно. Все остальные имена имеют тип ноль (в некоторых учебниках по Ассемблеру это трактуется как отсутствие типа, при этом гово­рится, что у таких имен атрибута Type нет, однако операция Ассемблера type выдает для таких имен значение ноль).

  • Атрибут значения Value. Этот атрибут определен только для имен сегментов, а также для имен числовых констант и числовых переменных периода генерации.

Рассмотрим теперь пример маленького, синтаксически правильного, но, вообще говоря, бес­смысленного (не головного) программного модуля на Ассемблере, для этого модуля будет построена таблица имен пользователя:

На рис. 12.1 показана таблица имен пользователя, которая построится синтаксическим анализа­тором при просмотре показанной выше программы до предложения

mov ax,Data

включительно. Атрибуты, которые не могут быть у данного имени, отмечены прочерком.

Как Вы уже знаете, значения атрибутов некоторых имен остаются неизвестными на этапе компи­ляции, они будут определяться позже во время редактирования внешних связей и загрузки програм­мы на выполнение. Такие значения обозначены в этой таблице как i16=?. Для каждого заносимого в таблицу имени, кроме атрибутов, ещё запоминается и позиция в предложении, на котором встрети­лось это имя (т.е. отмечается, что имя уже описано или объявлено, или же только использовано до своего описания или объявления).

Итак, теперь синтаксический анализатор рассматривает предложение

mov cx,R

В этой команде используется имя R, однако этого имени ещё нет в таблице имён. В этом случае оно заносится туда (ниже приведена строка таблицы для этого имени) Этого имени не было в таблице потому, что оно ещё не описано и про него ничего неизвестно, поэтому и все поля атрибутов в табли­це имеют пока неопределенные значения:

R

?

?

?

?

Соответственно, невозможно определить и точный код операции команды mov cx,R , так как второй операнд этой команды, вообще говоря, может иметь любой из допустимых для этой команды форматов r16,m16 или i16. На этом примере ярко видна необходимость второго просмотра тек­ста программы: только на втором просмотре, после получения информации об атрибутах имени R, возможно определить правильный формат этой команды (а значит, в частности, определить и длину этой команды в байтах).

После полного просмотра программы синтаксический анализатор построит таблицу имен, пока­занную на рис. 12.2.

На этапе синтаксического анализа выявляются все синтаксические ошибки в программе, например:

и т.д. Используя таблицу имен, синтаксический анализатор легко обнаруживает ошибки следующего вида:

1. Неописанное имя. Имя не является внешним (extrn), но встречается только в позиции ис­пользования (в поле операндов) и ни разу не встречается в поле метки.

2. Дважды описанное имя. Одно и то же имя дважды (или большее число раз) встретилось в по­ле метки.

3. Описанное, но не используемое имя. Это предупредительное сообщение может выдаваться некоторыми "продвинутыми" Ассемблерами, если имя определено в поле метки, но ни разу не встретилось в поле операндов и отсутствует в параметрах директив public (видимо, про­граммист что-то напутал).

Кроме того, синтаксический анализатор может обнаружить и ошибки, относящиеся к модулю в целом, например, превышение максимальной длины некоторого сегмента, отсутствие сегмента стека в головном модуле, использование метки команды (т.е. имени с двоеточием) в предложении резерви­рования памяти и т.д.

Итак, на втором проходе синтаксический анализатор обнаруживает все ошибки в программном модуле и однозначно определяет формат каждой команды. Теперь можно приступать к последнему этапу работы транслятора - генерации объектного модуля. На этом этапе все числа преобразуются во внутреннее машинное представление, выписывается битовое представление всех команд, оформляет­ся паспорт объектного модуля. Далее полученный объектный модуль записывается в библиотеку объектных модулей (обычно куда-нибудь в дисковую память).

Дополенение: двухпроходный транслят.