Chapter_12 (1110564), страница 5
Текст из файла (страница 5)
Таким образом,exitm прекращает обработку той части макроопределения, которая ограничена ближайшей при просмотревниз директивой endm.12(т.е. им отводится место в памяти Макропроцессора). У нас переменные периода генерации порождаются автоматически, при присваивании им первого значения. Так, в нашем макроопределениилокальная переменная периода генерации с именем K порождается при выполнении макрооператораприсваивания K=0, при этом ей, естественно, присваивается нулевое значение.Следующая важная компонента макросредств Ассемблера – это макроциклы (которые, конечно,должны быть в макросредствах, как в любом "солидном" алгоритмическом языке высокого уровня). 1В нашем макроопределении мы использовали один из видов макроциклов с именем irp.
Этот макроцикл называется циклом с параметром, он очень похож на цикл с параметром языка Паскаль и имеет такой синтаксис (параметр цикла мы назвали именем i):irp i,<список цикла>тело циклаendmПараметр цикла является локальной в этом цикле переменной периода генерации, которая, в отличие от переменных периода генерации, определяемых пользователем как показано выше, принимает строковые значения. Список цикла (он заключается в угловые скобки) является последовательностью (возможно пустой) текстовых строк, разделённых запятыми (напомним, что в Макропроцессорестроки не заключаются в апострофы). В нашем последнем макроопределении такой список макроцикла<al,ah,bl,bh,cl,ch,dl,dh,AL,AH,BL,BH,CL,CH,DL,DH,Al,Ah,Bl,Bh,Cl,Ch,Dl,Dh,aL,aH,bL,bH,cL,cH,dL,dH>Этот список содержит 32 двухбуквенные текстовые строки.
Вообще говоря, его необходимо записывать в виде одного предложения Макроассемблера (что возможно, так как максимальная длина предложения в нашем Ассемблере около 130 символов), но мы для удобства изобразили его в две строки.Выше, при написании текста макроопределения oint мы записали этот список даже в виде четырёхстрок, что, конечно, тоже неправильно и сделано только для удобства восприятия нашего примера.Выполнение макроцикла с именем irp производится по следующему правилу.
Сначала переменной цикла присваивается первое значение из списка цикла (первая строка текста), после чего выполняется тело цикла, при этом все вхождения в это тело имени параметра цикла заменяются на текущее значение этой переменой цикла. После этого параметру цикла присваивается следующее значение из списка цикла и т.д. Ясно, что для пустого списка тело цикла не будет выполняться ни одногораза. В нашем примере тело цикла будет выполняться не более 32-х раз, при этом переменная i будетпоследовательно принимать значения двухсимвольных строк текста al,ah,bl и т.д.Как можно заметить, целью выполнения макроцикла в нашем примере является присваиваниепеременной периода генерации K значения единицы, если параметр макрокоманды совпадает по написанию с именем одного из коротких регистров нашего компьютера, причём это имя может задаваться как большими, так и малыми буквами алфавита в любой комбинации.
Это позволяет распознать имя короткого регистра, как бы его ни записал пользователь, и присвоить переменной K значение единица, в противном случае переменная K сохраняет нулевое значение.2 После присваиванияпеременной периода генерации значения K=1 дальнейшее выполнение цикла бессмысленно, и мывыходим из него по директиве exitm за ближайшую вниз директиву endm.Обратите внимание, что макроцикл завершается такой же директивой endm, как и всё макроопределение. Это позволяет рассматривать макроцикл как некоторое стандартное макроопределение,только без заголовка (т.е.
без имени и без параметров).Далее в макроопределении расположен условный макрооператор нового для нас вида, который,однако, наиболее похож на условный оператор языка Паскаль:if <логическое выражение>ветвь then1Заметим, что в языках высокого уровня обычно есть далеко не все из рассматриваемых нами макросредств.2Для удобства программирования в некоторые Макроассемблеры введены условные макрооператоры, которые сравнивают две строки на равенство и неравенство без учёта регистра (при этом большие и маленькиелатинские буквы не различаются).
Например, такие условные макрооператоры есть в старших версиях нашегоМакроассемблера MASM, их использование, конечно, существенно облегчает программирование макроопределений.13elseветвь elseendifНа этом примере мы познакомимся с логическими выражениями Макропроцессора. Эти выражения весьма похожи на логические выражения Паскаля, только вместо логических констант true иfalse используются соответственно целые числа 1 и 0, а вместо знаков операций отношения используются, как и в некоторых других языках (например, в Фортране операция равенство обозначается как .EQ.), мнемонические двухбуквенные имена, которые перечислены ниже:1EQ=вместоNEвместо <>LT<вместоLEвместо <=GT>вместоGEвместо >=Таким образом, заголовок нашего условного макрооператораif K EQ 1 or type X EQ byteэквивалентен такой записи на Паскалеif (K=1)or (type X = byte) thenЗаметим, что в Паскале для этого примера нам необходимо использовать круглые скобки, так какоперация отношения = имеет меньший приоритет, чем операция логического сложения or.
В Макроассемблере же, наоборот, операции отношения (EQ,GT и т.д.) имеют более высокий приоритет,чем логические операции (or,and и not), а так как оператор type имеет больший приоритет, чемоператор EQ, то круглые скобки не нужны. По учебнику [5] Вам необходимо обязательно изучитьуровни приоритета всех операторов Ассемблера.Таким образом, наш условный макрооператор после вычисления логического выражения получает значение true, если K=1 (т.е. параметр макрокоманды – это короткий регистр r8) или же дляслучая, когда type X EQ byte (т.е. параметр макрокоманды задан выражением, которое имеетформат m8).
В остальных случаях (для параметра форматов r16,m16,i16) логическое выражениеимеет значение false. Когда это логическое выражение равно true, наше макроопределение вычисляет и помещает на регистр ax целочисленное значение, подлежащее выводу. И так как теперьэто значение имеет формат r16, то для его вывода можно использовать уже известную нам макрокоманду outint, а для значения false просто выводить значение параметра X.2Необходимо также заметить, что операции отношения LT,GT,LE и GE, как правило, рассматривают свои операнды как беззнаковые значения. Исключением является случай, когда Макропроцессор "видит", что некоторой переменной периода генерации явно присвоено отрицательное значение(т.е.
в присваиваемой константе есть знак минус). Например, рассмотрим следующий фрагмент программы:mov ax,ax; Чтобы была метка, type L=-1K = type L; Макропроцессор "видит" беззнаковое L=0FFFFhif K LТ 0; Берётся K=0FFFFh > 0 ==> false !. . .K = -1; Макропроцессор "видит" знаковую константу -1if K LT 0; Берётся K=-1 < 0 ==> true !L:1В отличие от Паскаля, эти операции отношения можно применять только к целочисленным операндам.Другими словами нельзя, например, написать сравнение на равенство текстовых строкif <X> EQ <ax>Для вычисления логического выражения от строковых значений, как мы уже знаем, используются специальныеусловные макрооператоры (ifb, ifidn, ifdif и некоторые другие).2Для простоты наше макроопределение никогда не задаёт второй параметр макрокоманды outint – ширину поля для вывода целого числа.14.
. .Как видим, этот вопрос в нашем Макроассемблере весьма запутан, его надо тщательно изучить,например, по учебнику [5].Не следует, конечно, думать, что мы написали совсем уж универсальное макроопределение длявывода любых целых чисел, которое всегда выдаёт либо правильный результат, либо диагностику обошибке в своих параметрах. К сожалению, наше макроопределение не будет выводить значения аргументов форматов m8 и m16, если эти аргументы заданы без имён, по которым можно определить ихтип, например, вызов oint [bx] , будет считаться ошибочным. Это связано с тем, что ошибкувызовет оператор type [bx].Кроме того, например, при вызове с помощью макрокомандыoint --8будет получено макрорасширениеmov ax,--8outint ax(так как type ––8 = 0).
К сожалению, наш Макропроцессор не предоставляет хороших средств,позволяющих выявить синтаксические ошибки такого рода в макрокомандах.1 Показанные вышеошибки будут выявлены уже компилятором с Ассемблера при анализе полученного макрорасширения.Далее, Вам важно понять принципиальное отличие переменных языка Ассемблера и переменныхпериода генерации. Так, например, переменная Ассемблера с именем X может, например, определяться предложением резервирования памяти в сегменте данныхXdw13В то время как переменная периода генерации с именем Y может порождаться макрооператором присваиванияY = 13Главное – это уяснить, что эти переменные имеют разные и непересекающиеся временá существования.
Переменные периода генерации существуют только во время компиляции исходного модуляс языка Ассемблер на объектный язык (т.е. во время генерации объектного модуля, отсюда и их название), и заведомо уничтожаются до начала счёта. В то же время описанные в сегментах статическиепеременные Ассемблера, наоборот, существуют только во время счёта программы (от начало счётадо выполнения макрокоманды finish). Некоторые программисты не понимают этого и пытаютсяиспользовать значение переменной Ассемблера на этапе компиляции, например, пишут такой неправильный условный макрооператор:if X EQ 13Это сразу показывает, что они не понимают суть дела, так как на этапе компиляции хотят анализировать значение переменной X, которая будет существовать только во время счёта программы.
Необходимо понять, что на этапе компиляции значение есть не у переменной с именем X, а только у имени переменной X (значение имени X равно адресу этой переменной в соответствующем сегменте).Кроме того, как мы знаем, на этапе компиляции почти все имена имеют тип, например, наше имя Xимеет тип word=2).В следующем примере мы покажем, как макроопределение может обрабатывать макрокоманды спеременных числом фактических параметров. Задачи такого рода часто встают перед программистом.