Макросредства Ассемблера

## Макросредства языка ассемблера

- Понятие о макросредствах языка ассемблера
- Псевдооператоры equ и =
- Макрокоманды
- Макродирективы
- Директивы условной компиляции

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

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

Если бы мы писали программу на машинном языке, то данные проблемы были бы принципиально не решаемыми. Но язык ассемблера, являясь символическим аналогом машинного языка, предоставляет для их решения ряд средств.
Основной целью, которая при этом преследуется, является повышение удобства написания программ.
В общем случае эта цель достигается по нескольким направлениям за счет следующего:

- расширения набора директив;
- введения некоторых дополнительных команд, не имеющих аналогов в системе команд микропроцессора. За примером далеко ходить не нужно — команды setfield и getfield, которые скрывают от программиста рутинные действия и генерируют наиболее эффективный код;
- введения сложных типов данных.

Но это все глобальные направления, по которым развивается сам транслятор от версии к версии.
Что же делать программисту для решения его локальной задачи, для облегчения работы в определенной проблемной области?
Для этого разработчики компиляторов ассемблера включают в язык и постоянно совершенствуют аппарат макросредств. Этот аппарат является очень мощным и важным.

В общем случае есть смысл говорить о том, что транслятор ассемблера состоит из двух частей — непосредственно транслятора, формирующего объектный модуль, и макроассемблера (рис. 1).

Рис. 1. Макроассемблер в общей схеме трансляции программы на TASM

Если вы знакомы с языком С или С++, то конечно помните широко применяемый в них механизм препроцессорной обработки. Он является некоторым аналогом механизма заложенного в работу макроассемблера. Для тех, кто ничего раньше не слышал об этих механизмах, поясню их суть.
Основная идея — использование подстановок, которые замещают определенным образом организованную символьную последовательность другой символьной последовательностью. Создаваемая таким образом последовательность может быть как последовательностью, описывающей данные, так и последовательностью программных кодов. Главное здесь то, что на входе макроассемблера может быть текст программы весьма далекий по виду от программы на языке ассемблера, а на выходе обязательно будет текст на чистом ассемблере, содержащем символические аналоги команд системы машинных команд микропроцессора.

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

Далее мы обсудим основной набор макросредств, доступных при использовании компилятора TASM. Отметим, что большинство этих средств доступно и в компиляторе с языка ассемблера фирмы Microsoft.
Обсуждение начнем с простейших средств и закончим более сложными.

### Псевдооператоры equ и =

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

Синтаксис псевдооператора equ:

`

    имя_идентификатора	equ	строка или числовое_выражение

`

Синтаксис псевдооператора “=”:

`

    имя_идентификатора	=	числовое_выражение

`

Несмотря на внешнее и функциональное сходство псевдооператоры equ и “=” отличаются следующим:

- из синтаксического описания видно, что с помощью equ идентификатору можно ставить в соответствие как числовые выражения, так и текстовые строки, а псевдооператор “=” может использоваться только с числовыми выражениями;
- идентификаторы, определенные с помощью “=”, можно переопределять в исходном тексте программы, а определенные с использованием equ — нельзя.

Ассемблер всегда пытается вычислить значение строки, воспринимая ее как выражение. Для того чтобы строка воспринималась именно как текстовая, необходимо заключить ее в угловые скобки: <строка>.
Кстати сказать, угловые скобки являются оператором ассемблера, с помощью которого транслятору сообщается, что заключенная в них строка должна трактоваться как текст, даже если в нее входят служебные слова ассемблера или операторы. Хотя в режиме Ideal это не обязательно, так как строка для equ в нем всегда трактуется как текстовая.

Псевдооператор equ удобно использовать для настройки программы на конкретные условия выполнения, замены сложных в обозначении объектов, многократно используемых в программе, более простыми именами и т. п.
К примеру:

`

    masm
    model	small
    stack	256
    mas_size	equ	10	;размерность массива
    akk	equ	ax		;переименовать регистр
    mas_elem	equ	mas[bx][si]	;адресовать элемент массива
    .data
    ;описание массива из 10 байт:
    mas	db	mas_size dup (0)
    .code
    	mov	akk,@data	;фактически mov ax,@data
    	mov	ds,akk		;фактически mov ds,ax
    ...
    	mov	al,mas_elem	;фактически — mov al,mas[bx][si]

`

Псевдооператор “=” удобно использовать для определения простых абсолютных (то есть не зависящих от места загрузки программы в память) математических выражений.
Главное условие то, чтобы транслятор мог вычислить эти выражения во время трансляции.
К примеру:

`

    .data
    adr1	db	5 dup (0)
    adr2	dw	0
    len = 43
    len = len+1	;можно и так, через предыдущее определение
    len = adr2-adr1

`

Как видно из примера, в правой части псевдооператора “=” можно использовать метки и ссылки на адреса — главное, чтобы в итоге получилось абсолютное выражение.

Компилятор TASM, начиная с версии 3.00, содержит директивы, значительно расширяющие его возможности по работе с текстовыми макросами. Эти директивы аналогичны некоторым функциям обработки строк в языках высокого уровня. Под строками здесь понимается текст, описанный с помощью псевдооператора equ.

Набор этих директив следующий:

- директива слияния строк catstr:
  идентификатор catstr строка_1,строка_2,... — значением этого макроса будет новая строка, состоящая из сцепленной слева направо последовательности строк строка_1,строка_2,...
  В качестве сцепляемых строк могут быть указаны имена ранее определенных макросов.
  К примеру:

  `

      pre	equ	Привет,
      name	equ	< Юля>
      privet	catstr	pre,name	;privet= “Привет, Юля”

  `

- директива выделения подстроки в строке substr:
  идентификатор substr строка,номер_позиции,размер — значением данного макроса будет часть заданной строки, начинающаяся с позиции с номером номер_позиции и длиной, указанной в размер.
  Если требуется только остаток строки, начиная с некоторой позиции, то достаточно указать только номер_позиции без указания размера.
  К примеру:

  `

      ;продолжение предыдущего фрагмента:
      privet	catstr	pre,name	;privet= “Привет, Юля”
      name	substr	privet,7,3	;name=“Юля”

  `

- директива определения вхождения одной строки в другую instr:
  идентификатор instr номер_нач_позиции,строка_1,строка_2 — после обработки данного макроса транслятором идентификатору будет присвоено числовое значение, соответствующее номеру (первой) позиции, с которой совпадают строка_1 и строка_2.
  Если такого совпадения нет, то идентификатор получит значение 0;
- директива определения длины строки в текстовом макросе sizestr:
  идентификатор sizestr строка — в результате обработки данного макроса значение идентификатор устанавливается равным длине строки.

  `

      ;как продолжение предыдущего фрагмента:
      privet	catstr	pre,name	;privet= “Привет, Юля”
      len	sizestr	privet	;len=10

  `

Эти директивы очень удобно использовать при разработке макрокоманд, которые являются следующим макросредством, предоставляемым компилятором ассемблера.

### Макрокоманды

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

Представим ситуацию, когда необходимо выполнить некоторые повторяющиеся действия. Программа из листинга 1 является ярким этому примером.

`

    Листинг 1. Пример программы на ассемблере
    <1> ;---------Prg_3_1.asm----------------------------------
    <2> ;Программа преобразования двузначного шестнадцатеричного числа
    <3> ;в символьном виде в двоичное представление.
    <4> ;Вход: исходное шестнадцатеричное число из двух цифр,
    <5> ;вводится с клавиатуры.
    <6> ;Выход: результат преобразования должен
    <7> ;быть в регистре al.
    <8> ;------------------------------------------------------
    <9> data segment para public 'data'	;сегмент данных
    <10> message	db	'Введите две шестнадцатеричные цифры,$'
    <11> data ends
    <12> stk segment stack
    <13> 	db	256 dup ('?')	;сегмент стека
    <14> stk ends
    <15> code segment para public 'code'	;начало сегмента кода
    <16> main	proc	;начало процедуры main
    <17> 	assume cs:code,ds:data,ss:stk
    <18> 	mov	ax,data	;адрес сегмента данных в регистр ax
    <19> 	mov	ds,ax	;ax в ds
    <20> 	mov	ah,9
    <21> 	mov	dx,offset message
    <22> 	int	21h
    <23> 	xor	ax,ax	;очистить регистр ax
    <24> 	mov	ah,1h	;1h в регистр ah
    <25> 	int	21h	;генерация прерывания с номером 21h
    <26> 	mov	dl,al	;содержимое регистра al в регистр dl
    <27> 	sub	dl,30h	;вычитание: (dl)=(dl)-30h
    <28> 	cmp	dl,9h	;сравнить (dl) с 9h
    <29> 	jle	M1	;перейти на метку M1 если dl<9h или dl=9h
    <30> 	sub	dl,7h	;вычитание: (dl)=(dl)-7h
    <31> M1:		;определение метки M1
    <32> 	mov	cl,4h	;пересылка 4h в регистр cl
    <33> 	shl	dl,cl	;сдвиг содержимого dl на 4 разряда влево
    <34> 	int	21h	;вызов прерывания с номером 21h
    <35> 	sub	al,30h	;вычитание: (dl)=(dl)-30h
    <36> 	cmp	al,9h	;сравнить (al) с 9h	28
    <37> 	jle	M2	;перейти на метку M2, если al<9h или al=9h
    <38> 	sub	al,7h	;вычитание: (al)=(al)-7h
    <39> M2:			;определение метки M2
    <40> 	add	dl,al	;сложение: (dl)=(dl)+(al)
    <41> 	mov	ax,4c00h	;пересылка 4c00h в регистр ax
    <42> 	int	21h	;вызов прерывания с номером 21h
    <43> main endp		;конец процедуры main
    <44> code ends		;конец сегмента кода
    <45> end main		;конец программы с точкой входа main

`

Структурно в ней явно прослеживаются повторяющиеся участки кода. Их можно оформить в виде макрокоманд и использовать эти повторяющиеся фрагменты в различных программах.
Дальнейшее наше обсуждение будет посвящено тому, как это сделать.

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

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

`

    имя_макрокоманды macro список_формальных_аргументов

    тело макроопределения

    endm

`

Где должны располагаться макроопределения?
Есть три варианта:

1. В начале исходного текста программы до сегмента кода и данных с тем, чтобы не ухудшать читабельность программы.
   Этот вариант следует применять в случаях, если определяемые вами макрокоманды актуальны только в пределах одной этой программы.
2. В отдельном файле.
   Этот вариант подходит при работе над несколькими программами одной проблемной области. Чтобы сделать доступными эти макроопределения в конкретной программе, необходимо в начале исходного текста этой программы записать директиву include имя_файла, к примеру:

   `

       masm
       model	small
       include	show.inc
       ;в это место будет вставлен текст файла show.inc
       ...

   `

3. В макробиблиотеке.
   Если у вас есть универсальные макрокоманды, которые используются практически во всех ваших программах, то их целесообразно записать в так называемую макробиблиотеку. Сделать актуальными макрокоманды из этой библиотеки можно с помощью все той же директивы include.

Недостаток двух последних способов в том, что в исходный текст программы включаются абсолютно все макроопределения.
Для исправления ситуации можно использовать директиву purge, в качестве операндов которой через запятую перечисляются имена макрокоманд, которые не должны включаться в текст программы.
К примеру,

`

    ...
    include	iomac.inc
    purge	_outstr,_exit
    ...

`

В данном случае в исходный текст программы перед началом компиляции TASM вместо строки include iomac.inc вставит строки из файла iomac.inc. Но вставленный текст будет отличаться от оригинала тем, что в нем будут отсутствовать макроопределения _outstr и _exit.

А теперь вернемся к программе из листинга 1. Проанализируем ее текст, выявим повторяющиеся участки и составим для них макроопределения (листинг 2).

`

    Листинг 2. Пример 1 создания и использования макрокоманд
    <1>;prg_3_1.asm с макроопределениями
    <2>init_ds	macro

    <3>;Макрос настройки ds на сегмент данных
    <4>	mov	ax,data
    <5>	mov	ds,ax
    <6>	endm

    <7>out_str	macro	str
    <8>;Макрос вывода строки на экран.
    <9>;На входе — выводимая строка.
    <10>;На выходе - сообщение на экране.
    <11>	push	ax
    <12>	mov	ah,09h
    <13>	mov	dx,offset str
    <14>	int	21h
    <15>	pop	ax
    <16>	endm
    <17>
    <18>clear_r	macro	rg
    <19>;очистка регистра rg
    <20>	xor	rg,rg
    <21>	endm
    <22>
    <23>get_char	macro
    <24>;ввод символа
    <25>;введенный символ в al
    <26>	mov	ah,1h
    <27>	int	21h
    <28>	endm
    <29>
    <30>conv_16_2	macro
    <31>;макрос преобразования символа шестнадцатеричной цифры
    <32>;в ее двоичный эквивалент в al
    <33>	sub	dl,30h
    <34>	cmp	dl,9h
    <35>	jle	$+5
    <36>	sub	dl,7h
    <37>	endm
    <38>
    <39>exit	macro
    <40> ;макрос конца программы
    <41>	mov	ax,4c00h
    <42>	int	21h
    <43>	endm
    <44>
    <45> data	segment para public 'data'
    <46> message	db	'Введите две шестнадцатеричные цифры (буквы A,B,C,D,E,F — прописные): $'
    <47> data	ends
    <48>
    <49> stk	segment	stack
    <50>	db	256 dup('?')
    <51> stk	ends
    <52>
    <53> code	segment para public 'code'
    <54>	assume	cs:code,ds:data,ss:stk
    <55> main	proc
    <56>	init_ds
    <57>	out_str	message

    <58>	clear_r	ax
    <59>	get_char
    <60>	mov	dl,al
    <61>	conv_16_2
    <62>	mov	cl,4h
    <63>	shl	dl,cl
    <64>	get_char
    <65>	conv_16_2
    <66>	add	dl,al
    <67>	xchg	dl,al	;результат в al
    <68>	exit
    <69> main	endp
    <70> code	ends
    <71> end	main

`

В листинге 2 в строках 3–7, 9–18, 20–23, 25–30, 32–38, 40–44 описаны макроопределения. Их назначение приведено сразу после заголовка в теле каждого макроопределения.

Все эти макроопределения можно использовать и при написании других программ. Посмотрите на модернизированный исходный текст программы из листинга 3.1 в листинге 2 (строки 56–70). Если не обращать внимания на некоторые неясные моменты, то сам сегмент кода стал внешне более читабельным и даже можно сказать, что в нем появился какой то смысл.

Функционально макроопределения похожи на процедуры.
Сходство их в том, что и те, и другие достаточно один раз где-то описать, а затем вызывать их специальным образом. На этом их сходство заканчивается, и начинаются различия, которые в зависимости от целевой установки можно рассматривать и как достоинства и как недостатки:

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

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

`

    	имя_макрокоманды	список_фактических_аргументов

`

Результатом применения данной синтаксической конструкции в исходном тексте программы будет ее замещение строками из конструкции тело макроопределения. Но это не простая замена.
Обычно макрокоманда содержит некоторый список аргументов — список_фактических_аргументов, которыми корректируется макроопределение.
Места в теле макроопределения, которые будут замещаться фактическими аргументами из макрокоманды, обозначаются с помощью так называемых формальных аргументов.
Таким образом, в результате применения макрокоманды в программе формальные аргументы в макроопределении замещаются соответствующими фактическими аргументами; в этом и заключается учет контекста.
Процесс такого замещения называется макрогенерацией, а результатом этого процесса является макрорасширение.

К примеру, рассмотрим самое короткое макроопределение в листинге 2 — clear_rg.
Как отмечено выше, результаты работы макроассемблера можно узнать, просмотрев файл листинга после трансляции. Покажем несколько его фрагментов, которые демонстрируют, как был описан текст макроопределения clear_rg (строки 24-27), как был осуществлен вызов макрокоманды clear_rg с фактическим параметром ax (строка 58) и как выглядит результат работы макрогенератора, сформировавшего команду ассемблера xor ax,ax (строка 75);

`

    24		clear_r	macro	rg
    25	;очистка регистра rg
    26		xor	rg,rg
    27		endm
    ...
    74		clear_r	ax
    75000E 33 C0	xor	ax,ax

`

Таким образом в итоге мы получили то, что и требовалось — команду очистки заданного регистра, в данном случае ax.
В другом месте программы вы можете выдать ту же макрокоманду, но уже с другим именем регистра.

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

- строка может состоять из:
  - последовательности символов без пробелов, точек, запятых, точек с запятой;
  - последовательности любых символов, заключенных в угловые скобки: <...>. В этой последовательности можно указывать как пробелы, так и точки, запятые, точки с запятыми.
    Не забывайте о том, что угловые скобки < > — это тоже оператор ассемблера. Мы упоминали о них при обсуждении директивы equ;
- для того чтобы указать, что некоторый символ внутри строки, представляющей фактический параметр, является собственно символом, а не чем-то иным, например некоторым разделителем или ограничивающей скобкой, применяется специальный оператор “!”.
  Этот оператор ставится непосредственно перед описанным выше символом, и его действие эквивалентно заключению данного символа в угловые скобки (см. предыдущий пункт);
- если требуется вычисление в строке некоторого константного выражения, то в начале этого выражения нужно поставить знак “%”:
  % константное_выражение — значение константное_выражение вычисляется и подставляется в текстовом виде в соответствии с текущей системой счисления.

Теперь обсудим вопрос — как транслятор распознает формальные аргументы в теле макроопределения для их последующей замены на фактические аргументы?

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

Здесь нужно еще раз особо отметить список_формальных_аргументов в заголовке макроопределения.
В общем случае он содержит не только перечисление формальных аргументов через запятую, но и некоторую дополнительную информацию. Полный синтаксис формального аргумента следующий:

`

    	имя_формального_аргумента[:тип]

`

где тип может принимать значения:

- REQ, которое говорит о том, что требуется обязательное явное задание фактического аргумента при вызове макрокоманды;
- =<любая_строка> — если аргумент при вызове макрокоманды не задан, то в соответствующие места в макрорасширении будет вставлено значение по умолчанию, соответствующее значению любая_строка.
  Будьте внимательны: символы, входящие в любая_строка, должны быть заключены в угловые скобки.

Но не всегда ассемблер может распознать в теле макроопределения формальный аргумент. Это, например, может произойти в случае, когда он является частью некоторого идентификатора. В этом случае последовательность символов формального аргумента отделяют от остального контекста с помощью специального символа &.
Этот прием часто используется для задания модифицируемых идентификаторов и кодов операций.
К примеру, определим макрос, который предназначен для генерации в программе некоторой таблицы, причем параметры этой таблицы можно задавать с помощью аргументов макрокоманды:

`

    ...
    def_table macro	type=b,len=REQ
    tabl_&type	d&type	len dup (0)
    endm
    ...
    .data
    def_tabl	b,10
    def_tabl	w,5

`

После того как вы подвергнете трансляции текст программы, содержащий эти строки, вы получите следующие макрорасширения:

`

    tabl_b	db	10 dup (0)
    tabl_w	dw	10 dup (0)

`

Символ & можно применять и для распознавания формального аргумента в строке, заключенной в кавычки ' '. Например:

`

    num_char	macro	message
    ;...
    ;подсчитать количество (num) символов в строке
    	jmp	m1
    elem	db	'Строка &message содержит '
    ;число символов в строке message в коде ASCII
    num	db	2 dup (0)
    	db	' символов',10,13,'$'	;конец строки для вывода функцией 09h
    m1:
    ;...
    ;вывести elem на экран
    	endm

`

В связи с рассмотрением последнего фрагмента разберем ситуацию, когда тело макроопределения содержит метку или имя в директиве резервирования и инициализации данных. Если в программе некоторая макрокоманда вызывается несколько раз, то в процессе макрогенерации возникнет ситуация, когда в программе один идентификатор будет определен несколько раз, что, естественно, будет распознано транслятором как ошибка. Для выхода из подобной ситуации применяют директиву local, которая имеет следующий синтаксис:

`

    local список_идентификаторов

`

Эту директиву необходимо размещать непосредственно за заголовком макроопределения.
Результатом работы этой директивы будет генерация в каждом экземпляре макрорасширения уникальных имен для всех идентификаторов, перечисленных в список_идентификаторов. Эти уникальные имена имеют вид ??xxxx, где хххх — шестнадцатеричное число.
Для первого идентификатора в первом экземпляре макрорасширения хххх= 0000, для второго — хххх= 0001 и т. д. Контроль за правильностью размещения и использования этих уникальных имен берет на себя ассемблер.

Для того чтобы вам окончательно все стало понятно, введем и подвергнем трансляции листинг 3. В нем, кроме некоторых ранее рассмотренных макрокоманд, содержится макрокоманда num_char. Ее назначение — подсчитывать количество символов в строке, адрес которой передается этой макрокоманде в качестве фактического параметра. Строка должна удовлетворять требованию, предъявляемому к строке, предназначенной для вывода на экран функцией 09h прерывания 21h, то есть заканчиваться символом $.
Другой момент, который нашел отражение в этой программе, — использование символа $ для распознавания формального аргумента в строке, заключенной в кавычки ' ' (см. последний фрагмент).

Листинг 3. Пример 2 создания и использования макрокоманд

`

    ;prg_13_2.asm
    init_ds	macro
    ;макрос настройки ds на сегмент данных
    	mov	ax,data
    	mov	ds,ax
    	xor	ax,ax
    	endm
    out_str	macro	str
    ;макрос вывода строки на экран.
    ;На входе — выводимая строка.
    ;На выходе — сообщение на экране.
    	push	ax
    	mov	ah,09h
    	mov	dx,offset str
    	int	21h
    	pop	ax
    	endm
    exit	macro
    ;макрос конца программы
    	mov	ax,4c00h
    	int	21h
    	endm
    num_char	macro	message
    	local
    m1,elem,num,err_mes,find,num_exit
    ;макрос подсчета количества символов в строке.
    ;Длина строки — не более 99 символов.
    ;Вход: message — адрес строки символов, ограниченной '$'
    ;Выход: в al — количество символов в строке message и вывод сообщения
    	jmp	m1
    elem	db	'Строка &message содержит '
    num	db	2 dup (0) ;число символов в строке message в коде ASCII
    	db	' символов',10,13,'$'	;конец строки для вывода функцией 09h
    err_mes	db	'Строка &message не содержит символа конца строки',10,13,'$'
    m1:
    ;сохраняем используемые в макросе регистры
    	push	es
    	push	cx
    	push	ax
    	push	di
    	push	ds
    	pop	es	;настройка es на ds
    	mov	al,'$'	;символ для поиска — `$`
    	cld		;сброс флага df
    	lea	di,message	;загрузка в es:di смещения строки message
    	push	di	;запомним di — адрес начала строки
    	mov	cx,99	;для префикса repne — максимальная длина строки
    ;поиск в строке (пока нужный символ и символ в строке не равны)
    ;выход — при первом совпавшем
    repne	scasb
    	je	find	;если символ найден — переход на обработку
    ;вывод сообщения о том, что символ не найден
    	push	ds
    ;подставляем cs вместо ds для функции 09h (int21h)
    	push	cs
    	pop	ds
    	out_str	err_mes
    	pop	ds
    	jmp	num_exit	;выход из макроса
    find:	;совпали
    ;считаем количество символов в строке:
    	pop	ax	;восстановим адрес начала строки
    	sub	di,ax	;(di)=(di)-(ax)
    	xchg	di,ax	;(di) <-> (ax)
    	sub	al,3	;корректировка на служебные символы — 10, 13, '$'
    	aam		;в al две упакованные BCD-цифры результата подсчета
    	or	ax,3030h	;преобразование результата в код ASCII
    	mov	cs:num,ah
    	mov	cs:num+1,al
    ;вывести elem на экран
    	push	ds
    ;подставляем cs вместо ds для функции 09h (int21h)
    	push	cs
    	pop	ds
    	out_str	elem
    	pop	ds
    num_exit:
    	push	di
    	push	ax
    	push	cx
    	push	es
    	endm

    data	segment	para public 'data'
    msg_1	db	'Строка_1 для испытания',10,13,'$'
    msg_2	db	'Строка_2 для второго испытания',10,13,'$'
    data ends

    stk	segment	stack
    	db	256 dup('?')
    stk	ends

    code	segment	para public 'code'
    	assume	cs:code,ds:data,ss:stk
    main	proc
    	init_ds
    	out_str	msg_1
    	num_char	msg_1
    	out_str	msg_2
    	num_char	msg_2
    	exit
    main	endp
    code	ends
    end	main

`

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

`

    mes	macro	messsage
    ...		;этот комментарий будет включен в текст листинга
    ...		;;этот комментарий не будет включен в текст листинга
    	endm

`

### Макродирективы

С помощью макросредств ассемблера можно не только частично изменять входящие в макроопределение строки, но и модифицировать сам набор этих строк и даже порядок их следования. Сделать это можно с помощью набора макродиректив (далее — просто директив). Их можно разделить на две группы:

- директивы повторения WHILE, REPT, IRP и IRPC.
  Директивы этой группы предназначены для создания макросов, содержащих несколько идущих подряд одинаковых последовательностей строк. При этом возможна частичная модификация этих строк.
- директивы управления процессом генерации макрорасширения EXITM и GOTO.
  Они предназначены для управления процессом формирования макрорасширения из набора строк соответствующего макроопределения. С помощью этих директив можно как исключать отдельные строки из макрорасширения, так и вовсе прекращать процесс генерации. Директивы EXITM и GOTO обычно используются вместе с условными директивами компиляции, поэтому они будут рассмотрены вместе с ними.

#### Директивы WHILE и REPT

Директивы WHILE и REPT применяют для повторения определенное количество раз некоторой последовательности строк.
Эти директивы имеют следующий синтаксис:

`

    WHILE	константное_выражение
    последовательность_строк
    ENDM

`

`

    REPT	константное_выражение
    последовательность строк
    ENDM

`

Обратите внимание, что последовательность повторяемых строк в обеих директивах ограничена директивой ENDM.

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

Директива REPT, подобно директиве WHILE, повторяет последовательность_строк столько раз, сколько это определено значением константное_выражение. Отличие этой директивы от WHILE состоит в том, что она автоматически уменьшает на единицу значение константное_выражение после каждой итерации.

В качестве примера рассмотрим листинг 4, в котором демонстрируется применение директив WHILE и REPT для резервирования области памяти в сегменте данных. Имя идентификатора и длина области задаются в качестве параметров для соответствующих макросов def_sto_1 и def_sto_2.

`

    Листинг 4. Использование директив повторения
    ;prg_13_3.asm
    def_sto_1	macro	id_table,ln:=<5>
    ;макрос резервирования памяти длиной len.
    ;Используется WHILE
    id_table	label	byte
    len=ln
    	while	len
    	db	0
    	len=len-1
    	endm
    endm
    def_sto_2	macro	id_table,len
    ;макрос резервирования памяти длиной len
    id_table	label	byte
    	rept	len
    	db	0
    	endm
    endm

    data	segment	para public 'data'
    	def_sto_1	tab_1,10
    	def_sto_2	tab_2,10
    data	ends
    ;сегменты команд и стека в этой программе необязательны
    end

`

Заметьте, что счетчик повторений в директиве REPT уменьшается автоматически после каждой итерации цикла. Проанализируйте результат трансляции листинга 13.3.

Таким образом, директивы REPT и WHILE удобно применять для “размножения” в тексте программы последовательности одинаковых строк без внесения в эти строки каких-либо изменений.

Следующие две директивы, IRP и IRPC, делают этот процесс более гибким, позволяя модифицировать на каждой итерации некоторые элементы в последовательность_строк.

#### Директива IRP

Директива IRP имеет следующий синтаксис:

`

    IRP формальный_аргумент,<строка_символов_1,...,строка_символов_N>
    	последовательность_строк
    ENDM

`

Действие данной директивы заключается в том, что она повторяет последовательность_строк N раз, то есть столько раз, сколько строк_символов заключено в угловые скобки во втором операнде директивы IRP. Но это еще не все.
Повторение последовательности_строк сопровождается заменой в ней формального_аргумента строкой символов из второго операнда.
Так, при первой генерации последовательности_строк формальный_аргумент в них заменяется на строка_символов_1.
Если есть строка_символов_2, то это приводит к генерации второй копии последовательности_строк, в которой формальный_аргумент заменяется на строка_символов_2. Эти действия продолжаются до строка_символов_N включительно.

К примеру, рассмотрим результат определения в программе следующей конструкции:

`

    	irp	ini,<1,2,3,4,5>
    	db	ini
    	endm

`

Макрогенератором будет сгенерировано следующее макрорасширение:

`

    	db	1
    	db	2
    	db	3
    	db	4
    	db	5

`

#### Директива IRPC

Директива IRPC имеет следующий синтаксис:

`

    IRPC	формальный_аргумент,строка_символов
    	последовательность строк
    ENDM

`

Действие данной директивы подобно IRP, но отличается тем, что она на каждой очередной итерации заменяет формальный_аргумент очередным символом из строка_символов.
Понятно, что количество повторений последовательность_строк будет определяться количеством символов в строка_символов.
К примеру:

`

    	irpc	rg,
    	push	rg&x
    	endm

`

В процессе макрогенерации эта директива развернется в следующую последовательность строк:

`

    	push	ax
    	push	bx
    	push	cx
    	push	dx

`

### Директивы условной компиляции

Последний тип макросредств — директивы условной компиляции.
Существует два типа этих директив:

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

С этими директивами применяются директивы управления процессом генерации макрорасширений EXITM и GOTO.

Директива EXITM не имеет операндов, и ее действие заключается в том, что она немедленно прекращает процесс генерации макрорасширения, начиная с того места, где она встретилась в макроопределении.

Директива GOTO имя_метки переводит процесс генерации макроопределения в другое место, прекращая тем самым последовательное разворачивание строк макроопределения. Метка, на которую передается управление, имеет специальный формат:

`

    :имя_метки

`

Примеры применения этих директив будут приведены ниже.

### Директивы компиляции по условию

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

Введение в язык ассемблера этих директив значительно повышает его мощь.
Всего имеется 10 типов условных директив компиляции. Их логично попарно объединить в четыре группы:

1. Директивы IF и IFE — условная трансляция по результату вычисления логического выражения.
2. Директивы IFDEF и IFNDEF — условная трансляция по факту определения символического имени.
3. Директивы IFB и IFNB — условная трансляция по факту определения фактического аргумента при вызове макрокоманды.
4. Директивы IFIDN, IFIDNI, IFDIF и IFDIFI — условная трансляция по результату сравнения строк символов.

Условные директивы компиляции имеют общий синтаксис и применяются в составе следующей синтаксической конструкции:

`

    IFxxx	логическое_выражение_или_аргументы
    фрагмент_программы_1
    	ELSE
    фрагмент_программы_2
    ENDIF

`

Заключение некоторых фрагментов текста программы — фрагмент_программы_1 и фрагмент_программы_2 — между директивами IFxxx, ELSE и ENDIF приводит к их выборочному включению в объектный модуль. Какой именно из этих фрагментов — фрагмент_программы_1 или фрагмент_программы_2 — будет включен в объектный модуль, зависит от конкретного типа условной директивы, задаваемого значением xxx, и значения условия, определяемого операндом (операндами) условной директивы логическое_выражение_или_аргумент(ы).

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

#### Директивы IF и IFE

Синтаксис этих директив следующий:

`

    IF(E)	логическое_выражение
    фрагмент_программы_1
    	ELSE
    фрагмент_программы_2
    ENDIF

`

Обработка этих директив макроассемблером заключается в вычислении логического_выражения и включении в объектный модуль фрагмент_программы_1 или фрагмент_программы_2 в зависимости от того, в какой директиве IF или IFE это выражение встретилось:

- если в директиве IF логическое выражение истинно, то в объектный модуль помещается фрагмент_программы_1.
  Если логическое выражение ложно, то при наличии директивы ELSE в объектный код помещается фрагмент_программы_2. Если же директивы ELSE нет, то вся часть программы между директивами IF и ENDIF игнорируется и в объектный модуль ничего не включается. Кстати сказать, понятие истинности и ложности значения логического_выражения весьма условно. Ложным оно будет считаться, если его значение равно нулю, а истинным — при любом значении, отличном от нуля.
- директива IFE аналогично директиве IF анализирует значение логического_выражения. Но теперь для включения фрагмент_программы_1 в объектный модуль требуется, чтобы логическое_выражение имело значение “ложь”.

Директивы IF и IFE очень удобно использовать при необходимости изменения текста программы в зависимости от некоторых условий.

К примеру, составим макрос для определения в программе области памяти длиной не более 50 и не менее 10 байт (листинг 5).

`

    Листинг 5. Использование условных директив IF и IFE
    <1>;prg_13_4.asm
    <2>masm
    <3>model	small
    <4>	stack	256
    <5>	def_tab_50	macro	len
    <6>if	len	GE 50
    <7>GOTO	exit
    <8>endif
    <9>	if	len LT 10
    <10>:exit
    <11>EXITM
    <12>endif
    <13>rept	len
    <14>	db	0
    <15>endm
    <16>endm
    <17>.data
    <18>def_tab_50	15
    <19> def_tab_50	5
    <20>.code
    <21>main:
    <22>	mov	ax,@data
    <23>	mov	ds,ax
    <24>exit:
    <25>	mov	ax,4c00h
    <26>	int	21h
    <27>end	main
    ENDIF

`

Введите и оттранслируйте листинг 5. При этом не забывайте о том, что условные директивы действуют на шаге трансляции, и поэтому результат их работы можно увидеть только после макрогенерации, то есть в листинге программы.
В нем вы увидите, что в результате трансляции строка 18 листинга 5 развернется в пятнадцать нулевых байт, а строка 19 оставит макрогенератор совершенно равнодушным, так как значение фактического операнда в строках 6 и 9 будет ложным. Обратите внимание, что для обработки реакции на ложный результат анализа в условной директиве мы использовали макродирективы EXITM и GOTO.

Другой интересный и полезный вариант применения директив IF и IFE — отладочная печать.
Суть здесь в том, что в процессе отладки программы почти всегда возникает необходимость динамически отслеживать состояние определенных программно- аппаратных объектов, в качестве которых могут выступать переменные, регистры микропроцессора и т. п. После этапа отладки отпадает необходимость в таких диагностических сообщениях. Для их устранения нужно корректировать исходный текст программы, после чего ее следует подвергнуть повторной трансляции. Но есть более изящный выход.
Можно определить в программе некоторую переменную, к примеру debug, и использовать ее совместно с условными директивами IF или IFE. К примеру,

`

    <1>...
    <2>debug	equ	1
    <3>...
    <4>.code
    <5>...
    <6>if	debug
    <7>;любые команды и директивы ассемблера
    <8>;(вывод на печать или монитор)
    <9>endif

`

На время отладки и тестирования программы вы можете заключить отдельные участки кода в своеобразные операторные скобки в виде директив IF и ENDIF (строки 6-9 последнего фрагмента), которые реагируют на значение логической переменной debug. При значении debug = 0 транслятор полностью проигнорирует текст внутри этих условных операторных скобок; при debug = 1, наоборот, будут выполнены все действия, описанные внутри них.

#### Директивы IFDEF и IFNDEF

Синтаксис этих директив следующий:

`

    IF(N)DEF	символическое_имя
    фрагмент_программы_1
    	ELSE
    фрагмент_программы_2
    ENDIF

`

Данные директивы позволяют управлять трансляцией фрагментов программы в зависимости от того, определено или нет в программе некоторое символическое_имя. Директива IFDEF проверяет, описано или нет в программе символическое_имя, и если это так, то в объектный модуль помещается фрагмент_программы_1. В противном случае, при наличии директивы ELSE, в объектный код помещается фрагмент_программы_2.
Если же директивы ELSE нет (и символическое_имя в программе не описано), то вся часть программы между директивами IF и ENDIF игнорируется и в объектный модуль не включается.

Действие IFNDEF обратно IFDEF. Если символического_имени в программе нет, то транслируется фрагмент_программы_1. Если оно присутствует, то при наличии ELSE транслируется фрагмент_программы_2. Если ELSE отсутствует, а символическое_имя в программе определено, то часть программы, заключенная между IFNDEF и ENDIF, игнорируется.

В качестве примера рассмотрим ситуацию, когда в объектный модуль программы должен быть включен один из трех фрагментов кода. Какой из трех фрагментов будет включен в объектный модуль, зависит от значения некоторого идентификатора switch:

- если switch = 0, то сгенерировать фрагмент для вычисления выражения
  y = x*2**n;
- если switch = 1, то сгенерировать фрагмент для вычисления выражения
  y = x/2**n;
- если switch не определен, то ничего не генерировать.

Соответствующий фрагмент исходной программы может выглядеть так:

`

    ifndef	sw	;если sw не определено, то выйти из макроса
    EXITM
    else		;иначе — на вычисление
    	mov	cl,n
    ife	sw
    	sal	x,cl	;умножение на степень 2 сдвигом влево
    	else
    	sar	x,cl	;деление на степень 2 сдвигом вправо
    	endif
    endif

`

Как видим, эти директивы логически связаны с директивами IF и IFE, то есть их можно применять в тех же самых случаях, что и последние.
Есть еще одна интересная возможность использования этих директив. На уроке 4 мы обсуждали формат командной строки и говорили об опциях, которые в ней можно задавать. Вспомните одну из опций командной строки TASM — опцию
/dидентификатор=значение.
Ее использование дает возможность управлять значением идентификатора прямо из командной строки транслятора, не изменяя при этом текста программы.
В качестве примера рассмотрим листинг 6, в котором мы попытаемся с помощью макроса контролировать процесс резервирования и инициализации некоторой области памяти в сегменте данных.

`

    Листинг 6. Инициализация значения идентификатора из командной строки
    <1>;prg_13_5.asm
    <2>masm
    <3>model	small
    <4>	stack	256
    <5>def_tab_50	macro	len
    <6>ifndef	len
    <7>display	'size_m не определено, задайте значение 10exitm
    <9>else
    <10>if	len GE 50
    <11>GOTO exit
    <12>endif
    <13>if	len LT 10
    <14>:exit
    <15>EXITM
    <16>endif
    <17>rept	len
    <18>	db	0
    <19>endm
    <20>endif
    <21>endm
    <22>;size_m=15
    <23>.data
    <24>def_tab_50	size_m
    <25>
    <26>.code
    <27>main:
    <28>	mov	ax,@data
    <29>	mov	ds,ax
    <30>exit:
    <31>	mov	ax,4c00h
    <32>	int	21h
    <33>end	main

`

Запустив этот пример на трансляцию, вы получите сообщение о том, что забыли определить значение переменной size_m. После этого попробуйте два варианта действий:

1. Определите где-то в начале исходного текста программы значение этой переменной с помощью equ:

   `

       size_m equ 15

   `

2. Запустите программу на трансляцию командной строкой вида
   tasm /dsize_m=15 /zi prg_13_2,,,

В листинге 6 мы использовали еще одну возможность транслятора — директиву display, с помощью которой можно формировать пользовательское сообщение в процессе трансляции программы.

#### Директивы IFB и IFNB

Синтаксис этих директив следующий:

`

    IF(N)B	аргумент
    фрагмент_программы_1
    	ELSE
    фрагмент_программы_2
    ENDIF

`

Данные директивы используются для проверки фактических параметров, передаваемых в макрос. При вызове макрокоманды они анализируют значение аргумента, и в зависимости от того, равно оно пробелу или нет, транслируется либо фрагмент_программы_1, либо фрагмент_программы_1. Какой именно фрагмент будет выбран, зависит от кода директивы:

- Директива IFB проверяет равенство аргумента пробелу. В качестве аргумента могут выступать имя или число.
  Если его значение равно пробелу (то есть фактический аргумент при вызове макрокоманды не был задан), то транслируется и помещается в объектный модуль фрагмент_программы_1.
  В противном случае, при наличии директивы ELSE, в объектный код помещается фрагмент_программы_1. Если же директивы ELSE нет, то при равенстве аргумента пробелу вся часть программы между директивами IFB и ENDIF игнорируется и в объектный модуль не включается.
- Действие IFNB обратно IFB. Если значение аргумента в программе не равно пробелу, то транслируется фрагмент_программы_1.
  В противном случае, при наличии директивы ELSE, в объектный код помещается фрагмент_программы_1. Если же директивы ELSE нет, то вся часть программы (при неравенстве аргумента пробелу) между директивами IFNB и ENDIF игнорируется и в объектный модуль не включается.

В качестве типичного примера применения этих директив предусмотрим строки в макроопределении, которые будут проверять, указывается ли фактический аргумент при вызове соответствующей макрокоманды:

`

    show	macro	reg
    ifb
    display	'не задан регистр'
    exitm
    endif
    ...
    endm

`

Если теперь в сегменте кода вызвать макрос show без аргументов, то будет выведено сообщение о том, что не задан регистр и генерация макрорасширения будет прекращена директивой exitm.

#### Директивы IFIDN, IFIDNI, IFDIF и IFDIFI

Эти директивы позволяют не просто проверить наличие или значение аргументов макрокоманды, но и выполнить идентификацию аргументов как строк символов.

Синтаксис этих директив:

`

    IFIDN(I)	аргумент_1,аргумент_2
    фрагмент_программы_1
    	ELSE
    фрагмент_программы_2
    ENDIF

`

`

    IFDIF(I)	аргумент_1,аргумент_2
    фрагмент_программы_1
    	ELSE
    фрагмент_программы_2
    ENDIF

`

В этих директивах проверяются аргумент_1 и аргумент_2 как строки символов. Какой именно код — фрагмент_программы_1 или фрагмент_программы_1 — будет транслироваться по результатам сравнения, зависит от кода директивы.
Парность этих директив объясняется тем, что они позволяют учитывать либо не учитывать различие строчных и прописных букв. Так, директивы IFIDNI и IFDIFI игнорируют это различие, а IFIDN и IFDIF — учитывают.

Директива IFIDN(I) сравнивает символьные значения аргумент_1 и аргумент_2.
Если результат сравнения положительный, то фрагмент_программы_1 транслируется и помещается в объектный модуль.
В противном случае, при наличии директивы ELSE, в объектный код помещается фрагмент_программы_1.
Если же директивы ELSE нет, то вся часть программы между директивами IFIDN(I) и ENDIF игнорируется и в объектный модуль не включается.

Действие IFDIF(I) обратно IFIDN(I).
Если результат сравнения отрицательный (строки не совпадают), транслируется фрагмент_программы_1.
В противном случае все происходит аналогично рассмотренным ранее директивам.

Как мы уже упоминали, эти директивы удобно применять для проверки фактических аргументов макрокоманд.
К примеру, проверим, какой из регистров — al или ah — передан в макрос в качестве параметра (проверка проводится без учета различия строчных и прописных букв):

`

    show	macro	rg
    ifdifi	,
    goto	M_al
    else
    ifdifi	,
    goto	M_ah
    else
    exitm
    endif
    endif
    :M_al
    ...
    :M_ah
    ...
    endm
    ENDIF

`

#### Вложенность директив условной трансляции

Как мы неоднократно видели в приведенных выше примерах, TASM допускает вложенность условных директив компиляции. Более того, так как вложенность требуется довольно часто, TASM предоставляет набор дополнительных директив формата ELSEIFxxx, которые заменяют последовательность подряд идущих ELSE и IFxxx в структуре:

`

    IFxxx
    ;
    	ELSE
    	IFxxx
    ;...
    ENDIF
    ENDIF

`

Эту последовательность условных директив можно заменить эквивалентной последовательностью дополнительных директив:

`

    IFxxx
    ;...
    	ELSEIFxxx
    ;...
    ENDIF

`

Наличие xxx в ELSExxx говорит о том, что каждая из директив IF, IFB, IFIDN и т. д. имеет аналогичную директиву ELSEIF, ELSEIFB, ELSEIFIDN и т. д.
В конечном итоге это улучшает читаемость кода. В последнем примере фрагмента макроса, проверяющем, имя какого регистра было передано в макрос, наблюдается подобная ситуация. Последовательность ELSE и IFDIFI можно записать так, как в строке 4:

`

    <1>show	macro	rg
    <2>ifdifi	,
    <3>goto	M_al
    <4>elseifdifi	,
    <5>	goto	M_ah
    <6>else
    <7>exitm
    <8>endif
    <9>:M_al
    <10>...
    <11>:M_ah
    <12>...
    <13>endm

`

### Директивы генерации ошибок

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

Директивы генерации пользовательской ошибки по принципу работы можно разделить на два типа:

- безусловные директивы, генерирующие ошибку трансляции без проверки каких-либо условий;
- условные директивы, генерирующие ошибку трансляции после проверки определенных условий.

Большинство директив генерации ошибок имеют два обозначения, хотя принцип их работы одинаков. Второе название отражает их сходство с директивами условной компиляции. При дальнейшем обсуждении такие парные директивы будут приводиться в скобках.

#### Безусловная генерация пользовательской ошибки

К безусловным директивам генерации пользовательской ошибки относится только одна директива — это ERR (.ERR).

Данная директива, будучи вставлена в текст программы, безусловно приводит к генерации ошибки на этапе трансляции и удалению объектного модуля. Она очень эффективна при ее использовании с директивами условной компиляции или в теле макрокоманды с целью отладки.
К примеру, эту директиву можно было бы вставить в ту ветвь программы (в последнем рассмотренном нами макроопределении), которая выполняется, если указанный в качестве аргумента регистр отличен от al и ah:

`

    show	macro	rg
    ifdifi	,
    goto	M_al
    else
    ifdifi	,
    goto	M_ah
    else
    .Err
    endif
    endif
    ...
    endm

`

Если после определенного таким образом макроопределения в сегменте кода вызвать макрокоманду show с фактическим параметром, отличным от имен регистров ah или al, будет сгенерирована ошибка компиляции (с текстом “User error”), сам процесс компиляции прекращен и, естественно, объектный модуль создан не будет.

Остальные директивы являются условными, так как их поведение определяют некоторые условия.

#### Условная генерация пользовательской ошибки

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

- .ERRB (ERRIFB) и .ERRNB (ERRIFNB)
- .ERRDEF (ERRIFDEF) и .ERRNDEF (ERRIFNDEF)
- .ERRDIF (ERRIFDIF) и .ERRIDN (ERRIFIDN)
- .ERRE (ERRIFE) и .ERRNZ (ERRIF)

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

#### Директивы .ERRB (ERRIFB) и .ERRNB (ERRIFNB)

Синтаксис директив:

.ERRB (ERRIFB) <имя_формального_аргумента> — генерация пользовательской ошибки, если <имя_формального_аргумента> пропущено;

.ERRNB (ERRIFNB) <имя_формального_аргумента> — генерация пользовательской ошибки, если <имя_формального_аргумента> присутствует.

Данные директивы применяются для генерации ошибки трансляции в зависимости от того, задан или нет при вызове макрокоманды фактический аргумент, соответствующий формальному аргументу в заголовке макроопределения с именем <имя_формального_аргумента>.
По принципу действия эти директивы полностью аналогичны соответствующим директивам условной компиляции IFB и IFNB. Их обычно используют для проверки задания параметров при вызове макроса.

Строка имя_формального_аргумента должна быть заключена в угловые скобки.

К примеру, определим обязательность задания фактического аргумента, соответствующего формальному аргументу rg, в макросе show:

`

    <1>	show	macro	rg
    <2>;если rg в макрокоманде не будет задан,
    <3>;то завершить компиляцию
    <4>.errb
    <5>;текст макроопределения
    <6>;...
    <7>endm

`

#### Директивы .ERRDEF (ERRIFDEF) и .ERRNDEF (ERRIFNDEF)

Синтаксис директив:

.ERRDEF (ERRIFDEF) символическое_имя — если указанное имя определено до выдачи этой директивы в программе, то генерируется пользовательская ошибка.

.ERRNDEF(ERRIFNDEF) символическое_имя — если указанное символическое_имя не определено до момента обработки транслятором данной директивы, то генерируется пользовательская ошибка.

Данные директивы генерируют ошибку трансляции в зависимости от того, определено или нет некоторое символическое_имя в программе.
Не забывайте о том, что компилятор TASM по умолчанию формирует объектный модуль за один проход исходного текста программы. Следовательно, директивы .ERRDEF (ERRIFDEF) и .ERRNDEF (ERRIFNDEF) отслеживают факт определения символического_имени только в той части исходного текста, которая находится до этих директив.

#### Директивы .ERRDIF (ERRIFDIF) и .ERRIDN (ERRIFIDN)

Синтаксис директив:

.ERRDIF (ERRIFDIF) <строка_1>,<строка_2> — директива, генерирующая пользовательскую ошибку, если две строки посимвольно не совпадают. Строки могут быть символическими именами, числами или выражениями и должны быть заключены в угловые скобки. Аналогично директиве условной компиляции IFDIF, при сравнении учитывается различие прописных и строчных букв.

.ERRIDN (ERRIFIDN) <строка_1>,<строка_2> — директива, генерирующая пользовательскую ошибку, если строки посимвольно идентичны. Строчное и прописное написание одной и той же буквы воспринимается как разные символы.

Для того чтобы игнорировать различия строчных и прописных букв, существуют аналогичные директивы:

ERRIFDIFI <строка_1>,<строка_2> — то же, что и ERRIFDIF, но игнорируется различие строчных и прописных букв при сравнении <строка_1> и <строка_2>.

ERRIFIDNI <строка_1>,<строка_2> — то же, что и ERRIFIDN, но игнорируется различие строчных и прописных букв при сравнении <строка_1> и <строка_2>.

Данные директивы, как и соответствующие им директивы условной компиляции, удобно применять для проверки передаваемых в макрос фактических параметров.

#### Директивы .ERRE (ERRIFE) и .ERRNZ (ERRIF)

Синтаксис директив:

.ERRE (ERRIFE) константное_выражение — директива вызывает пользовательскую ошибку, если константное_выражение ложно (равно нулю). Вычисление константного_выражения должно приводить к абсолютному значению, и это выражение не может содержать компонентов, являющихся ссылками вперед.

.ERRNZ(ERRIF) константное_выражение — директива вызывает пользовательскую ошибку, если константное_выражение истинно (не равно нулю). Вычисление константного_выражения должно приводить к абсолютному значению и не может содержать компонентов, являющихся ссылками вперед.

### Константные выражения в условных директивах

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

`

    .data
    mas	db	...
    len	dd	...
    ...
    .code
    ...
    .erre	(len-mas) lt 10	;генерация ошибки, если длина
    			;области mas меньше 10 байт
    ...

`

Кроме того, выражение не должно содержать компоненты, которые транслятор еще не обработал к тому месту программы, где находится условная директива.
Также мы отметили, что логические результаты “истина” и “ложь” являются условными в том смысле, что ноль соответствует логическому результату “ложь”, а любое ненулевое значение — “истине”.
Но в языке ассемблера существуют операторы, которые позволяют сформировать и “чисто логический” результат. Это так называемые операторы отношений, выражающие отношение двух значений или константных выражений.
В контексте условных директив вместе с операторами отношений можно рассматривать и логические операторы. Результатом работы и тех, и других может быть одно из двух значений:

- истина — число, которое содержит двоичные единицы во всех разрядах;
- ложь — число, которое содержит двоичные нули во всех разрядах.

Операторы, которые можно применять в выражениях условных директив и которые формируют логические результаты, приведены в табл. 1 и 2.

##### Таблица 1. Операторы отношений

Оператор/ | Синтаксис | Результат отношения
EQ (equal) — равно | выражение_1 EQ выражение_2 | истина — если выражение_1 равно выражение_2
NE (not equal) — не равно | Выражение_1 NE выражение_2 | Истина — если выражение_1 не равно выражение_2
LT (less than) — меньше | Выражение_1 LT выражение_2 | Истина — если выражение_1 меньше выражение_2
LE (less or equal) — меньше или равно | Выражение_1 LE выражение_2 | Истина — если выражение_1 меньше или равно выражение_2
GT (greater than) — больше | Выражение_1 GT выражение_2 | Истина — если выражение_1 больше выражение_2
GE (greater or equal) — больше или равно | Выражение_1 GE выражение_2 | Истина — если выражение_1 больше или равно выражение_2

##### Таблица 2. Логические операторы

Оператор | Синтаксис | Результат
NOT — логическое отрицание | NOT выражение | Истина — если выражение ложно; ложь — если выражение истинно
AND — логическое И | выражение_1 AND выражение_2 | Истина — если выражение_1 и выражение_2 истинны
OR — логическое ИЛИ | выражение_1 OR выражение_2 | Истина — если выражение_1 или выражение_2 истинны
XOR — исключающее ИЛИ | выражение_1 XOR выражение_2 | Истина — если выражение_1 = (NOT выражение_2)

### Дополнительное управление трансляцией

TASM предоставляет средства для вывода текстового сообщения во время трансляции программы — директивы DISPLAY и %OUT. С их помощью можно, при необходимости, следить за ходом трансляции.
К примеру:

`

    display	недопустимые аргументы макрокоманды
    ...
    %out	недопустимое имя регистра

`

В результате обработки этих директив на экран будут выведены тексты сообщений. Если эти директивы использовать совместно с директивами условной компиляции, то, к примеру, можно отслеживать путь, по которому осуществляется трансляция исходного текста программы.