КаталогИндекс раздела
НазадОглавлениеВперед


Тема 3. Макропроцессоры.

Основные понятия

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

Определение, которое дает ГОСТ не представляется удачным, так как оно говорит только о сокращении объема записи, а это лишь одна из возможностей обеспечиваемых Макропроцессором. Хотя Макропроцессоры являются обязательным элементом всех современных языков Ассемблеров, аналогичные модули (Препроцессоры) могут быть и для других языков, в том числе и для языков высокого уровня. Для одних языков (Pascal, PL/1) применение средств препроцессора является опционным, для других (C, C++) - обязательным.

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

  1. Независимые. Препроцессор составляет отдельный программный модуль (независимую программу), выполняющую просмотр (один или несколько) исходного модуля и формирующую новый файл исходного модуля, поступающий на вход Транслятора (пример - язык C).
  2. Слабосвязанные. Препроцессор составляет с Транслятором одну программу, но разные секции этой программы. Если в предыдущем случае Препроцессор обрабатывает весь файл, а затем передает его Транслятору, то в этом случае единицей обработки является каждый оператор исходного текста: он обрабатывается секцией Препроцессора, а затем передается секции Транслятора. (Пример - HLASM для S/390).
  3. Сильносвязанные. То же распределение работы, что и в предыдущем случае, но Препроцессор использует некоторые общие с Транслятором структуры данных. Например, Макропроцессор может распознавать имена, определенные в программе директивой EQU и т.п. (Пример - MASM, TASM).

Основные термины, связанные с данными, обрабатываемыми Макропроцессором: макровызов (или макрокоманда), макроопределение, макрорасширение.

Макровызов или макрокоманда или макрос - оператор программы, который подлежит обработке Макропроцессором (как мы дальше увидим, Макропроцессор обрабатывает не все операторы, а только ему адресованные).

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

Макрорасширение - результат выполнения макровызова, представляющий собой один или несколько операторов языка Ассемблера, подставляемых в исходный модуль вместо оператора макровызова. Пример обработки макровызова показан на рисунке.

Оператор макровызова в исходной программе имеет тот же формат, что и другие операторы языка Ассемблера: В нем есть метка (необязательно), мнемоника и операнды. При обработке исходного текста если мнемоника оператора не распознается как машинная команда или директива, она считается макрокомандой и передается для обработки Макропроцессору.

Макроопределение описывает, как должна обрабатываться макрокоманда. Средства такого описания составляют некоторый Макроязык. Для Макропроцессоров 1-й и 2-й категорий средства Макроязыка могут быть достаточно развитыми. Для Макропроцессоров 3-й категории средства Макроязыка могут быть довольно бедными, но в составе языка Ассемблера может быть много директив, применяемых в макроопределениях (возможно, - только в макроопределениях). В теле макроопределения могут употребляться операторы двух типов:

Поскольку макроопределение, обрабатывается перед трансляцией или вместе с ней, макрокоманда, определенная в исходном модуле, может употребляться только в этом исходном модуле и "не видна" из других исходных модулей. Для повторно используемых макроопределений обычно создаются библиотеки макроопределений. В некоторых системах (например, z/OS) макрокоманды обеспечивают системные вызовы и существуют богатейшие библиотеки системных макроопределений.

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

Сравнение макросредств и подпрограмм

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

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

Некоторые возможности Макроязыка

Ниже мы описываем некоторые возможности макроязыка, в той или иной форме реализованные во всех Макропроцессорах. Мы, однако, ориентируемся прежде всего на Макропроцессор, независимый от Ассемблера, потому что в этой категории функции Макропроцессора легче определить.

Заголовок макроопределения

Макроопределение должно как-то выделяться в программе, поэтому оно всегда начинается с заголовка.

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

    имя_макрокоманды MACRO список формальных параметров
имя_макрокоманды является обязательным компонентом. При макровызове это имя употребляется в поле мнемоники оператора. Имена макроопределений, имеющихся в программе, должны быть уникальны. Обычно при распознавании макровызова поиск по имени макрокоманды ведется сначала среди макроопределений имеющихся в программе, а затем (если в программе такое макроопределение не найдено) - в библиотеках макроопределений. Таким образом, имя макрокоманды, определенной в программе, может совпадать с именем макрокоманды, определенной в библиотеке, в этом случае макрокоманда, определенная в программе, заменяет собой библиотечную.

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

В развитых Макроязыках возможны три формы задания параметров: позиционная, ключевая и смешанная. При использовании позиционной формы соответствие фактических параметров формальным определяется их порядковым номером. (Позиционная форма всегда применяется для подпрограмм).
Пример:

Заголовок макроопределения Макровызов Результат подстановки
M1 MACRO A,B,C M1 X,Y,Z A=X, B=Y, C=Z

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

    имя_параметра=значение_параметра

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

Заголовок макроопределения Макровызов Результат подстановки
M1 MACRO A=Q,B=,C=R M1 C=Z,B=X A=Q, B=X, C=Z

В смешанной форме первые несколько параметров подчиняются правилам позиционной формы, а остальные - ключевые.

Пример:
Заголовок макроопределения Макровызов Результат подстановки
M1 MACRO A,B,C=Q,D=,E=R M1 X,Y,Z,D=T,E=S A=X,B=Y,C=Q,D=T,E=S

В некоторых Макропроцессорах имена параметров начинаются с некоторого отличительного признака (например, амперсанда - &), чтобы Макропроцессор мог отличить "свои" имена (имена, подлежащие обработке при обработке макроопределения) от имен, подлежащих обработке Ассемблером. Для Макропроцессоров, которые мы отнесли к категории сильносвязанных такой признак может и не быть необходимым, так как такой Макропроцессор обрабатывает как свои имена, так и имена Ассемблера. В любом случае возникает проблема распознавания имени в теле макроопределения. Например, если макроопределение имеет формальный параметр &P, а в макровызове указано для него фактическое значение 'X', то как должна обрабатываться подстрока '&PA' в теле макроопределения? Должна ли эта подстрока быть заменена на 'XA' или оставлена без изменений?

Логика, которой следует большинство Макропроцессоров в этом вопросе, такова. &PA является именем в соответствии с правилами формирования имен. Поэтому оно не распознается как имя &P и остается без изменений. Если мы хотим, чтобы подстановка в этой подстроке все-таки произошла, следует поставить признак, отделяющий имя параметра от остальной части строки. Обычно в качестве такого признака используется точка - '.': '&P.A' заменяется на 'XA'.

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

Если у макроопределения есть начало (оператор MACRO), то у него, естественно, должен быть и конец. Конец макроопределения определяется оператором MEND. Этот оператор не требует параметров. Макроопределение, взятое в "скобки" MACRO - MEND может располагаться в любом месте исходного модуля, но обычно все макроопределения размещают в начале или в конце модуля.

Локальные переменные макроопределения

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

Переменные макроопределения могут использоваться двумя способами:

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

Для сильносвязанных Макропроцессоров необходимости в локальных переменных макроопределения, вместо них могут использоваться имена программы (определяемые директивой EQU). Для сильносвязанных и независимых процессоров переменный макроопределения и имена программы должны различаться, для этого может применяться тот же признак, что и для параметров макроопределения. Объявление локальной переменной макроопределения может иметь, например, вид:

    имя_переменной LOCL начальное_значение (последнее необязательно)

Присваивание значений переменным макроопределения

Присваивание может производиться оператором вида:

    имя_переменной SET выражение
или
    имя_переменной = выражение

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

  1. Ввести в оператор объявления переменной макроопределения определение ее типа. При выполнении операций должно проверяться соответствие типов.
  2. Все переменные макроопределения имеют строковый тип, но при вычислении выражений автоматически преобразуются к типу, требуемому для данной операции (при таком преобразовании может возникать ошибка). Результат выражения автоматически преобразуется в строку.

Как правило, операции присваивания могут применяться к параметрам макроопределения точно так же, как и к переменным макроопределения.

Глобальные переменные макроопределения

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

Объявление глобальной переменной макроопределения может иметь, например, вид:

    имя_переменной GLBL начальное_значение  (последнее необязательно)

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

Уникальные метки

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

Уникальное имя метки может формироваться формате, подобном следующему:

   &имя.nnnnnn
где - nnnnnn - число, увеличивающееся на 1 для каждой следующей уникальной метки.

Другой возможный способ формирования, например:

    имя&SYSNDX
где SYSNDX - предустановленное имя, имеющее числовое значение, начинающееся с 00001 и увеличивающееся на 1 для каждой следующей уникальной метки.

Следующие операторы Макроязыка влияют на последовательность обработки операторов макроопределения. В тех или иных Макропроцессорах имеется тот или иной набор таких операторов.

Оператор безусловного перехода и метки макроопределения

Возможный формат оператора:

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

Оператор условного перехода

Возможный формат оператора:

    MIF условное_выражение макрометка
Если условное_выражение имеет значение "истина", обработка переходит на оператор, помеченный макрометкой, иначе обрабатывается следующий оператор макроопределения. Условные выражения формируются по обычным правилам языков программирования. В них могут употребляться параметры и переменные (локальные и глобальные) макроопределения, константы, строковые, арифметические и логические операции и, конечно же, операции сравнения. Кроме того, в составе Макроязыка обычно имеются специальные функции, позволяющие распознавать тип своих операндов, например: является ли операнд строковым представлением числа, является ли операнд именем, является ли операнд именем регистра и т.п.

Условные блоки

Возможный формат оператора:

    IF условное_выражение
    операторы_макроопределения_блок1
    ENDIF
    ELSE
    операторы_макроопределения_блок2
    ENDIF

Если условное_выражение имеет значение "истина", обрабатываются операторы макроопределения от оператора IF до оператора ENDIF, иначе обрабатываются операторы макроопределения от оператора ESLE до оператора ENDIF. Как и в языках программирования блок ELSE - ENDIF не является обязательным.

Условные выражения описаны выше. Обычно предусматриваются специальные формы:

    IFDEF имя
    IFNDEF имя
проверяющие просто определено или не определено данное имя.

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

Операторы повторений

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

Как и в языках программирования, в Макроязыке может быть несколько форм операторов повторения, приведем некоторые (не все) из возможных форм:

  1.     MDO выражение
        блок_операторов_макроопределения
        ENDMDO
    
    выражение должно иметь числовой результат, обработка блока операторов повторяется столько раз, каков результат вычисления выражения.
  2.     MDOLIST переменная_макроопределения,список_выражений 
        блок_операторов_макроопределения
        ENDMDO
    
    обработка блока операторов повторяется столько раз, сколько элементов имеется в списке_выражений, при этом в каждой итерации переменной_макроопределения присваивается значение очередного элемента из списка_выражений.
  3.     MDOWHILE условное_выражение 
        блок_операторов_макроопределения
        ENDMDO
    
    обработка блока операторов повторяется до тех пор, пока значение условного_выражения - "истина".

Выдача сообщения

При возникновении ошибок или ситуаций, требующих предупреждения программисту в листинг должно выводиться сообщение. Если в результате ошибки программиста, написавшего макроопределение или макровызов будет сгенерирован неправильный код программы на языке Ассемблера, то эта ошибка будет выявлена только Ассемблером на этапе трансляции программы. Однако выгоднее выявлять ошибки не как можно более ранних этапах подготовки программы, в Макроязыке ошибочные ситуации (ошибки в параметрах и т.п.) могут быть выявлены при помощи условных операторов или блоков, а для выдачи сообщения об ошибке должен существовать специальный оператор Макроязыка. Формат такого оператора примерно следующий:

    MOTE код_серьезности,код_ошибки,сообщение_об_ошибке

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

код_ошибки - числовой код, служащий, например, для поиска развернутого описания сообщений и действий при его возникновении в документе "Сообщения программы"

сообщение_об_ошибке - текст, печатаемый в листинге

Завершение обработки

Обработка макроопределения завершается при достижении оператора MEND. Однако, поскольку алгоритм обработки макроопределения может разветвляться, должна быть предусмотрена возможность выхода из обработки и до достижения конца макроопределения. Эта возможность обеспечивается оператором MEXIT. Операндом этого оператора может быть код_серьезности.

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

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

Макрорасширения в листинге

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

Структуры данных Макропроцессора

Таблица макроопределений, строго говоря, не таблица, а просто массив строк, в который записываются тексты всех макроопределений (от оператора MACRO до оператора MEND), найденных в обрабатываемом модуле.

Таблица имен макроопределений содержит имена макроопределений и указатель на размещение текста макроопределения в таблице макроопределений, как показано на рисунке.

Таблица глобальных переменных имеет такую структуру:

Все таблицы имеют переменный размер и заполняются в процессе работы.

Индекс уникальных меток - число, используемое для формирования уникальной части имен меток, встречающихся в макроопределениях

Для обработки каждого макровызова создаются:

Таблица параметров, содержащая информацию о параметрах макроопределения.

Таблица локальных переменных, содержащая информацию о локальных переменных макроопределения.

Структура этих таблиц - такая же, как и таблицы глобальных переменных, эти две таблицы могут быть объединены в одну таблицу параметров и локальных переменных.

Таблица меток макроопределения, структура которой:

Алгоритм работы Макропроцессора

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

Ниже мы приводим алгоритм работы 2-проходного Макропроцессора, при этом мы исходим из следующих предпосылок:

Алгоритм выполнения 1-го прохода следующий:

  1. 1-й проход Макропроцессора
  2. Инициализация: открытие исходного файла, создание пустых таблиц, признак "обработка макроопределения" устанавливается в FALSE.
  3. Чтение следующей строки исходного файла с проверкой конца файла.
  4. Если при чтении строки найден конец файла, выводится сообщение об ошибке, закрываются файлы, освобождается память...
  5. ... и Макропроцессор завершается с признаком ошибки.
  6. Если конец файла не достигнут, выполняется лексический разбор прочитанной строки с выделением имени и мнемоники операции.
  7. Алгоритм Макропроцессора разветвляется в зависимости от мнемоники операции
  8. Если мнемоника операции MACRO - заголовок макроопределения, то в таблицу имен макроопределений заносится имя, находящееся в этом операторе и начальный адрес свободной области в таблице макроопределений. (При занесении имени в таблицу имен макроопределений проверяется, нет ли уже в таблице такого имени, если есть - ошибка)
  9. Оператор MACRO записывается в таблицу макроопределений.
  10. Признак "обработка макроопределения" устанавливается в TRUE.
  11. Если мнемоника операции MEND - конец макроопределения, то оператор записывается в таблицу макроопределений...
  12. ... и признак "обработка макроопределения" устанавливается в FALSE.
  13. Если мнемоника операции END - конец программы, то проверяется установка признака "обработка макроопределения".
  14. Если этот признак установлен в TRUE, т.е., конец программы встретился до окончания макроопределения, то выводится сообщение об ошибке, закрываются файлы, освобождается память...
  15. ... и Макропроцессор завершается с признаком ошибки.
  16. Если этот признак установлен в FALSE, то выполняются завершающие операции ...
  17. ... и заканчивается 1-й проход Макропроцессора.
  18. При любой другой мнемонике оператора проверяется установка признака "обработка макроопределения".
  19. Если этот признак установлен в TRUE, то оператор записывается в таблицу макроопределений, если признак установлен в FALSE, то оператор игнорируется Макропроцессором.

Алгоритм выполнения 2-го прохода следующий:

  1. 2-й проход макропроцессора
  2. Начальные установки: открытие файлов, создание пустых таблиц, etc. Признак режима обработки устанавливается в значение "обработка программы".
  3. Признак конца обработки установлен ?
  4. Если признак конца обработки установлен, выполняются завершающие операции...
  5. .. и работа Макропроцессора заканчивается.
  6. Выполняется разбор строки.
  7. Проверяется признак режима обработки.
  8. Если признак режима установлен в значение "обработка макроопределения", то проверяется мнемоника оператора.
  9. Если в режиме обработки макроопределения встречается мнемоника MEND, то режим переключается в "обработка программы", все прочие операторы в режиме обработки макроопределения игнорируются.
  10. Если признак режима работы установлен в значение "обработка программы", происходит ветвление алгоритма в зависимости от мнемоники оператора.
  11. Обработка оператора MACRO заключается в установке режима обработки в значение "обработка программы".
  12. Обработка директивы Ассемблера END заключается в установке признака окончания работы и выводе оператора в выходной файл.
  13. Любая другая мнемоника ищется в Таблице машинных команд и в Таблице директив Ассемблера. Если мнемоника найдена в одной из этих таблиц, то...
  14. ... оператор просто выводится в выходной файл.
  15. Если оператор не является оператором языка Ассемблера, то предполагается, что это макровызов и соответствующее мнемонике имя ищется в Таблице имен макроопределений.
  16. Если имя не найдено в Таблице имен макроопределений, то оно ищется в библиотеках макроопределений (см. ниже).
  17. Если имя не найдено и в библиотеках макроопределений, вырабатывается сообщение об ошибке и управление передается на чтение следующего оператора программы.
  18. Если имя не найдено в библиотеках макроопределений, соответствующие элементы включаются в Таблицу имен макроопределений и в Таблицу макроопределений.
  19. Если имя есть в Таблице макроопределений, выполняется обработка макровызова (см. ниже), после чего управление передается на чтение следующего оператора программы.

Алгоритм обработки макровызова следующий

  1. Обработка макровызова. На входе этого модуля есть номер элемента в Таблице имен макроопределений и разобранный текст оператора макровызова.
  2. Создание пустых: Таблицы локальных переменных, Таблицы меток.
  3. Чтение первой строки из Таблицы макроопределений по адресу, записанному в элементе Таблице имен макроопределений. (Здесь и далее мы подразумеваем, что после чтения очередной строки макроопределения указатель для следующего чтения устанавливается на адрес следующей строки, если он не изменен явным образом.)
  4. Проверка параметров: сопоставление фактических параметров вызова с формальными параметрами, описанными в заголовке макроопределения (Заголовок находится в строке, только что считанной из Таблицы макроопределений).
  5. При несоответствии фактических параметров формальным выдается сообщение об ошибке...
  6. ... и обработка макровызова завершается
  7. При правильном задании фактических параметров параметры и их значения заносятся в Таблицу локальных переменных.
  8. Создается и заполняется Таблица меток макроопределения. При этом текст макроопределения просматривается до оператора MEND, выявляются метки и заносятся в таблицу. Проверяется уникальность меток. После заполнения таблицы меток указатель чтения из Таблицы макроопределений устанавливается на вторую (следующую за заголовком строку) текста макроопределения.
  9. Читается следующая строка текста макроопределения.
  10. Если строка является комментарием Ассемблера, строка выводится в макрорасширение.
  11. Если строка является комментарием Макроязыка, управление передается на чтение следующей строки макроопределения.
  12. Выполняется разбор строки.
  13. Алгоритм ветвится в зависимости от мнемоники оператора.
  14. При обработке оператора LOCL имя локальной переменной ищется сначала в Таблице локальных переменных...
  15. ... а затем - в Таблице глобальных переменных.
  16. Если имя найдено в одной из таблиц, формируется сообщение о неуникальном имени.
  17. В противном случае заносится новая строка в таблицу локальных имен. В любом случае управление передается на чтение следующей строки макроопределения.
  18. Обработка оператора GLBL отличается от оператора LOCL только тем, что новая строка создается в Таблице глобальных переменных.
  19. При обработке оператора LOCL вычисляется выражение - операнд команды. Вычисление включает в себя подстановку значений входящих в выражение переменных. Возможны ошибки - из-за использования неопределенных имен и ошибок в синтаксисе выражения.
  20. Имя переменной ищется сначала в Таблице локальных переменных.
  21. Если имя найдено, изменяется его значение в Таблице локальных переменных.
  22. Если имя переменной не найдено, оно ищется в Таблице глобальных переменных.
  23. Если имя найдено в Таблице глобальных переменных, изменяется его значение в этой таблице.
  24. Если имя не найдено ни в одной из таблиц, формируется сообщение о неопределенном имени.
  25. При обработке оператора MIF вычисляется условное выражение - 1-й операнд команды (возможны ошибки).
  26. Проверяется значение вычисленного условного выражения.
  27. Если значение выражения "истина", имя метки - 2-го операнда команды ищется в Таблице меток макроопределения.
  28. Если метка найдена в таблице, указатель для следующего чтения из Таблице макроопределений устанавливается на адрес соответствующий метке
  29. Если метка найдена в таблице, выдается сообщение о неопределенной метке.
  30. При обработке оператора MGO имя метки - операнда команды ищется в Таблице меток макроопределения.
  31. Если метка найдена в таблице, указатель для следующего чтения из Таблице макроопределений устанавливается на адрес соответствующий метке.
  32. Если метка найдена в таблице, выдается сообщение о неопределенной метке.
  33. При обработке оператора MNOTE выводится сообщение, определяемое операндом.
  34. Устанавливается и анализируется код серьезности. Код серьезности является общим для всей работы Макропроцессора, его значение изменяется только, если новое значение больше текущего (более серьезная ошибка)
  35. Если код серьезности не допускает продолжения работы Макропроцессора, устанавливается признак завершения работы.
  36. При обработке оператора MEXIT устанавливается и анализируется код серьезности.
  37. Если код серьезности не допускает продолжения работы Макропроцессора, устанавливается признак завершения работы.
  38. Освобождаются структуры данных, созданные для обработки макровызова...
  39. ...и обработка макровызова завершается.
  40. При обработке оператора MEND освобождаются структуры данных, созданные для обработки макровызова...
  41. ...и обработка макровызова завершается.
  42. Любая другая мнемоника операции означает, что оператор является не оператором Макроязыка, а оператором языка Ассемблера. В этом случае прежде всего проверяется, не имеет ли оператор метки, которая должна быть уникальной.
  43. Если оператор имеет такую метку, формируется имя уникальной метки и индекс уникальных меток увеличивается на 1.
  44. Выполняются подстановки в операторе языка Ассемблера (значение имен ищутся в Таблицах локальных и глобальных переменных, возможны ошибки).
  45. Оператор языка Ассемблера записывается в макрорасширение.

Библиотеки макроопределений

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

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

Один из таких алгоритмов следующий:

Вложенные макровызовы. Вложенные макроопределения.

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

Макроопределения внутри макроопределений

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

Против такого средства можно привести 2 соображения:

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

Тем не менее, если вложенные макроопределения все же необходимы, можно предложить следующий вариант их реализации:

Макрокоманды внутри макроопределений

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

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

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

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

Качественное расширение возможностей.

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

Структурный Ассемблер

В виде макрокоманд могут быть реализованы операторы, близкие к операторам управления потоком вычисления в языках высокого уровня (условные операторы, ветвления, различные виды циклов). Известным примером такого расширения является язык Макроассемблера BCPL - предшественник языка C.

Объектно-ориентированный Ассемблер

Макросредства могут обеспечить и реализацию свойств объектно-ориентированного программирования - в большей или меньшей степени.

Простейшее расширение Ассемблера ОО свойствами предполагает введение макрокоманды определения объекта (или резервирования памяти для объекта). В макрокоманде указывается тип объекта и она употребляется вместо директив DC/BSS. Для типа могут быть созданы макрокоманды-операции. В этом варианте может быть воплощен принцип полиморфизма, так как одна и та же операция может быть допустимой для разных типов. (Например, одна команда сложения для всех типов - чисел, независимо от из разрядности и формы представления). Принцип инкапсуляции реализуется здесь в том смысле, что программист, использующий макрокоманды не должен знать внутренней структуры объекта и подробности выполнения операций над ним, защиту же внутренней структуры организовать гораздо сложнее.

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

Переносимый машинный язык

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


НазадОглавлениеВперед
КаталогИндекс раздела