Cамоучитель по Assembler

         

Об ассемблере

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

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

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


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

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

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

Типичный современный компьютер (на базе i486 или Pentium) состоит из следующих компонентов (рис. 1).

Рис. 1. Компьютер и периферийные устройства

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

Рис. 2. Структурная схема персонального компьютера

Обсудим схему на рис. 2 в несколько нетрадиционном стиле.
Человеку свойственно, встречаясь с чем-то новым, искать какие-то ассоциации, которые могут помочь ему познать неизвестное. Какие ассоциации вызывает компьютер? У меня, к примеру, компьютер часто ассоциируется с самим человеком. Почему?

У компьютера есть органы восприятия информации из внешнего мира — это клавиатура, мышь, накопители на магнитных дисках. На рис. 2 эти органы расположены справа от системных шин.
У компьютера есть органы “переваривающие” полученную информацию — это центральный процессор и оперативная память.
И, наконец, у компьютера есть органы речи, выдающие результаты переработки. Это также некоторые из устройств справа.

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

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

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

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

О книге

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

Из книги вы узнаете:

  • состав и внутреннюю структуру компьютера;
  • архитектуру современных микропроцессоров Intel;
  • основные принципы управления аппаратурой компьютера;
  • возможности системы команд микропроцессоров Intel;
  • наиболее эффективные и проверенные временем приемы программирования на языке ассемблера;
  • характеристику реального и защищенного режима работы микропроцессора.

    Прочитав книгу, вы научитесь:

  • использовать современные программные средства разработки программ на ассемблере;
  • правильно оформлять программы на ассемблере с учетом потребностей конкретной задачи;
  • наиболее эффективно и в полной мере использовать возможности, заложенные в систему команд микропроцессора;
  • использовать мощный аппарат макросредств;
  • использовать развитые структуры данных, характерные для языков высокого уровня;
  • разрабатывать многомодульные программы, в том числе с использованием модулей на языках Pascal и C;
  • разрабатывать программы обработки аппаратных и пользовательских прерываний с использованием всех возможностей, предоставляемых контроллером прерываний i8259A;
  • разрабатывать программы, использующие возможности защищенного режима, в том числе и обрабатывающие прерывания в этом режиме.

    Изложение материала в книге ведется в форме уроков.

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

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

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

    Вторая часть книги, начиная с урока 12, посвящена углубленному изучению вопросов программирования с использованием языка ассемблера.
    Так, на уроке 12 читатель подробно познакомится со средствами ассемблера для работы со структурами данных, работа с которыми обычно характерна для языков высокого уровня (таких как Pascal, C). Это несколько сближает уровень программирования на ассемблере с уровнем программирования на указанных языках.

    На уроке 13 читатель очень подробно познакомится с очень полезными средствами языка ассемблера — макросредствами. Именно наличие этих средств, при надлежащем овладении ими, может сделать процесс программирования на ассемблере не только легким, но и приятным.

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

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

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

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





    Предисловие

    Урок 1. Общие сведения об ЭВМ Урок 2. Архитектура персонального компьютера Архитектура ЭВМ
    Набор регистров
    Организация памяти
    Типы данных
    Формат команд
    Обработка прерываний Урок 3. Разработка простой программы на ассемблере Урок 4. Создание программы на ассемблере Создание объектного модуля (трансляция программы)
    Создание загрузочного модуля (компоновка программы)
    Отладчик Turbo Debugger Урок 5. Структура программы на ассемблере Синтаксис ассемблера
    Директивы сегментации
    Описание простых типов данных ассемблера Урок 6. Система команд микропроцессора Системы счисления
    Перевод чисел из одной системы счисления в другую
    Структура машинной команды
    Функциональная классификация машинных команд Урок 7. Команды обмена данными Команды пересылки данных
    Команды ввода-вывода в порт
    Команды работы с адресами и указателями
    Команды преобразования данных
    Команды работы со стеком Урок 8. Арифметические команды Обзор группы арифметических команд и данных
    Арифметические операции над целыми двоичными числами
    Вспомогательные команды для целочисленных операций
    Арифметические операции над двоично-десятичными числами Урок 9. Логические команды Логические данные
    Логические команды
    Команды сдвига
    Примеры работы с битовыми строками Урок 10. Команды передачи управления Безусловные переходы
    Условные переходы
    Организация циклов Урок 11. Цепочечные команды Пересылка цепочек
    Сравнение цепочек
    Сканирование цепочек
    Загрузка элемента цепочки в аккумулятор
    Перенос элемента из аккумулятора в цепочку
    Ввод элемента цепочки из порта ввода-вывода
    Вывод элемента цепочки в порт ввода-вывода Урок 12. Сложные структуры данных Массивы
    Структуры
    Объединения
    Записи Урок 13. Макросредства языка ассемблера Псевдооператоры equ и =
    Макрокоманды
    Макродирективы
    Директивы условной компиляции
    Константные выражения в условных директивах
    Дополнительное управление трансляцией Урок 14. Модульное программирование Технологии программирования
    Процедуры в языке ассемблера
    Связь ассемблера с языками высокого уровня Урок 15. Прерывания Контроллер прерываний
    Программирование контроллера прерываний i8259А
    Реальный режим работы микропроцессора Урок 16. Защищенный режим работы микропроцессора Системные регистры микропроцессора
    Структуры данных защищенного режима
    Пример программы защищенного режима Урок 17. Обработка прерываний в защищенном режиме Шлюз ловушки
    Шлюз прерывания
    Шлюз задачи
    Программирование контроллера прерываний i8259A
    Загрузка регистра IDTR Приложение 1. Опции транслятора TASM и редактора   связей TLINK  463 Приложение 2. Описание системы команд микропроцессоров Intel Приложение 3. Таблицы кодов символов Приложение 4. Функции прерываний 10h (BIOS) и 21h (DOS) Приложение 5. Директивы управления листингом Приложение 6. Значения полей инициализации Приложение 7. Библиотека арифметических подпрограмм Приложение 8. Пример работы со структурой Приложение 9. Текст макроопределения SHOW Приложение 10. Предупреждающие сообщения и сообщения об ошибках


    Пользовательские регистры

    Как следует из названия, пользовательскими регистры называются потому, что программист может использовать их при написании своих программ. К этим регистрам относятся (рис. 1): восемь 32-битных регистров, которые могут использоваться программистами для хранения данных и адресов (их еще называют регистрами общего назначения (РОН)):
  • eax/ax/ah/al;
  • ebx/bx/bh/bl;
  • edx/dx/dh/dl;
  • ecx/cx/ch/cl;
  • ebp/bp;
  • esi/si;
  • edi/di;
  • esp/sp. шесть регистров сегментов: cs, ds, ss, es, fs, gs; регистры состояния и управления:
  • регистр флагов eflags/flags;
  • регистр указателя команды eip/ip.

    Рис. 1. Пользовательские регистры микропроцессоров i486 и Pentium

    Почему многие из этих регистров приведены с наклонной разделительной чертой?
    Нет, это не разные регистры — это части одного большого 32-разрядного регистра. Их можно использовать в программе как отдельные объекты.
    Так сделано для обеспечения работоспособности программ, написанных для младших 16-разрядных моделей микропроцессоров фирмы Intel, начиная с i8086.
    Микропроцессоры i486 и Pentium имеют в основном 32-разрядные регистры. Их количество, за исключением сегментных регистров, такое же, как и у i8086, но размерность больше, что и отражено в их обозначениях — они имеют
    приставку e (Extended).

    Разберемся подробнее с составом и назначением пользовательских регистров.

    Регистры общего назначения

    Все регистры этой группы позволяют обращаться к своим
    “младшим” частям (см.
    рис. 1).
    Рассматривая этот рисунок, заметьте, что использовать для самостоятельной адресации можно только младшие 16 и 8-битные части этих регистров. Старшие 16 бит этих регистров как самостоятельные объекты недоступны. Это сделано, как мы отметили выше, для совместимости с младшими 16-разрядными моделями микропроцессоров фирмы Intel.

    Перечислим регистры, относящиеся к группе регистров общего назначения. Так как эти регистры физически находятся в микропроцессоре внутри арифметико-логического устройства (АЛУ), то их еще называют регистрами АЛУ: eax/ax/ah/al (Accumulator register) — аккумулятор.
    Применяется для хранения промежуточных данных. В некоторых командах использование этого регистра обязательно; ebx/bx/bh/bl (Base register) — базовый регистр.
    Применяется для хранения базового адреса некоторого объекта в памяти; ecx/cx/ch/cl (Count register) — регистр-счетчик.
    Применяется в командах, производящих некоторые повторяющиеся действия. Его использование зачастую неявно и скрыто в алгоритме работы соответствующей команды.
    К примеру, команда организации цикла loop кроме передачи управления команде, находящейся по некоторому адресу, анализирует и уменьшает на единицу значение регистра ecx/cx; edx/dx/dh/dl (Data register) — регистр данных.
    Так же, как и регистр eax/ax/ah/al, он хранит промежуточные данные. В некоторых командах его использование обязательно; для некоторых команд это происходит неявно. Следующие два регистра используются для поддержки так называемых цепочечных операций, то есть операций, производящих последовательную обработку цепочек элементов, каждый из которых может иметь длину 32, 16 или 8 бит: esi/si (Source Index register) — индекс источника.
    Этот регистр в цепочечных операциях содержит текущий адрес элемента в цепочке-источнике; edi/di (Destination Index register) — индекс приемника (получателя).
    Этот регистр в цепочечных операциях содержит текущий адрес в цепочке-приемнике. В архитектуре микропроцессора на программно-аппаратном уровне поддерживается такая структура данных, как стек. Для работы со стеком в системе команд микропроцессора есть специальные команды, а в программной модели микропроцессора для этого существуют специальные регистры: esp/sp (Stack Pointer register) — регистр указателя стека.
    Содержит указатель вершины стека в текущем сегменте стека. ebp/bp (Base Pointer register) — регистр указателя базы кадра стека.
    Предназначен для организации произвольного доступа к данным внутри стека. Не спешите пугаться столь жесткого функционального назначения регистров АЛУ. На самом деле, большинство из них могут использоваться при программировании для хранения операндов практически в любых сочетаниях. Но, как мы отметили выше, некоторые команды используют фиксированные регистры для выполнения своих действий. Это нужно обязательно учитывать.
    Использование жесткого закрепления регистров для некоторых команд позволяет более компактно кодировать их машинное представление. Знание этих особенностей позволит вам при необходимости хотя бы на несколько байт сэкономить память, занимаемую кодом программы.

    Сегментные регистры

    В программной модели микропроцессора имеется шесть сегментных регистров: cs, ss, ds, es, gs, fs.
    Их существование обусловлено спецификой организации и использования оперативной памяти микропроцессорами Intel. Она заключается в том, что микропроцессор аппаратно поддерживает структурную организацию программы в виде трех частей, называемых сегментами. Соответственно, такая организация памяти называется сегментной.

    Для того чтобы указать на сегменты, к которым программа имеет доступ в конкретный момент времени, и предназначены сегментные регистры. Фактически, с небольшой поправкой, как мы увидим далее, в этих регистрах содержатся адреса памяти с которых начинаются соответствующие сегменты. Логика обработки машинной команды построена так, что при выборке команды, доступе к данным программы или к стеку неявно используются адреса во вполне определенных сегментных регистрах. Микропроцессор поддерживает следующие типы сегментов: Сегмент кода. Содержит команды программы.
    Для доступа к этому сегменту служит регистр cs (code segment register) — сегментный регистр кода. Он содержит адрес сегмента с машинными командами, к которому имеет доступ микропроцессор (то есть эти команды загружаются в конвейер микропроцессора). Сегмент данных. Содержит обрабатываемые программой данные.
    Для доступа к этому сегменту служит регистр ds (data segment register) — сегментный регистр данных, который хранит адрес сегмента данных текущей программы. Сегмент стека. Этот сегмент представляет собой область памяти, называемую стеком.
    Работу со стеком микропроцессор организует по следующему принципу: последний записанный в эту область элемент выбирается первым. Для доступа к этому сегменту служит регистр ss (stack segment register) — сегментный регистр стека, содержащий адрес сегмента стека. Дополнительный сегмент данных.
    Неявно алгоритмы выполнения большинства машинных команд предполагают, что обрабатываемые ими данные расположены в сегменте данных, адрес которого находится в сегментном регистре ds.
    Если программе недостаточно одного сегмента данных, то она имеет возможность использовать еще три дополнительных сегмента данных. Но в отличие от основного сегмента данных, адрес которого содержится в сегментном регистре ds, при использовании дополнительных сегментов данных их адреса требуется указывать явно с помощью специальных префиксов переопределения сегментов в команде.
    Адреса дополнительных сегментов данных должны содержаться в регистрах es, gs, fs (extension data segment registers).

    Регистры состояния и управления

    В микропроцессор включены несколько регистров (см. рис. 1), которые постоянно содержат информацию о состоянии как самого микропроцессора, так и программы, команды которой в данный момент загружены на конвейер. К этим регистрам относятся: регистр флагов eflags/flags; регистр указателя команды eip/ip. Используя эти регистры, можно получать информацию о результатах выполнения команд и влиять на состояние самого микропроцессора. Рассмотрим подробнее назначение и содержимое этих регистров:

    eflags/flags (flag register) — регистр флагов. Разрядность eflags/flags — 32/16 бит. Отдельные биты данного регистра имеют определенное функциональное назначение и называются флагами. Младшая часть этого регистра полностью аналогична регистру flags для i8086. На рис. 2 показано содержимое регистра eflags.

    Рис. 2. Содержимое регистра eflags

    Исходя из особенностей использования, флаги регистра eflags/flags можно разделить на три группы: 8 флагов состояния. Эти флаги могут изменяться после выполнения машинных команд.
    Флаги состояния регистра eflags отражают особенности результата исполнения арифметических или логических операций. Это дает возможность анализировать состояние вычислительного процесса и реагировать на него с помощью команд условных переходов и вызовов подпрограмм. В
    табл. 1 приведены флаги состояния и указано их назначение; 1 флаг управления. Обозначается df (Directory Flag).
    Он находится в 10-м бите регистра eflags и используется цепочечными командами. Значение флага df определяет направление поэлементной обработки в этих операциях: от начала строки к концу (df = 0) либо наоборот, от конца строки к ее началу (df = 1).
    Для работы с флагом df существуют специальные команды: cld (снять флаг df) и std (установить флаг df).
    Применение этих команд позволяет привести флаг df в соответствие с алгоритмом и обеспечить автоматическое увеличение или уменьшение счетчиков при выполнении операций со строками; 5 системных флагов, управляющих вводом/выводом, маскируемыми прерываниями, отладкой, переключением между задачами и виртуальным режимом 8086.
    Прикладным программам не рекомендуется модифицировать без необходимости эти флаги, так как в большинстве случаев это приведет к прерыванию работы программы. В табл. 2 перечислены системные флаги, их назначение.

    Таблица 1. Флаги состояния
    Мнемоника флагаФлагНомер бита в eflagsСодержание и назначение
    cfФлаг переноса
    (Carry Flag)
    0 1 — арифметическая операция произвела перенос из старшего бита результата. Старшим является 7, 15 или 31-й бит в зависимости от размерности операнда;
    0 — переноса не было
    pfФлаг паритета
    (Parity Flag)
    2 1 — 8 младших разрядов (этот флаг — только для 8 младших разрядов операнда любого размера) результата содержат четное число единиц;
    0 — 8 младших разрядов результата содержат нечетное число единиц
    afВспомогательный флаг переноса
    (Auxiliary carry Flag)
    4 Только для команд работающих с BCD-числами. Фиксирует факт заема из младшей тетрады результата:
    1 — в результате операции сложения был произведен перенос из разряда 3 в старший разряд или при вычитании был заем в разряд 3 младшей тетрады из значения в старшей тетраде;
    0 — переносов и заемов в(из) 3 разряд(а) младшей тетрады результата не было
    zfФлаг нуля (Zero Flag)6 1 — результат нулевой;
    0 — результат ненулевой
    sfФлаг знака
    (Sign Flag)
    7 Отражает состояние старшего бита результата (биты 7, 15 или 31 для 8, 16 или 32-разрядных операндов соответственно):
    1 — старший бит результата равен 1;
    0 — старший бит результата равен 0
    ofФлаг переполнения
    (Overflow Flag)
    11 Флаг of используется для фиксирования факта потери значащего бита при арифметических операциях:
    1 — в результате операции происходит перенос (заем) в(из) старшего, знакового бита результата (биты 7, 15 или 31 для 8, 16 или 32-разрядных операндов соответственно);
    0 — в результате операции не происходит переноса (заема) в(из) старшего, знакового бита результата
    ioplУровень Привилегий ввода-вывода
    (Input/Output Privilege Level)
    12, 13 Используется в защищенном режиме работы микропроцессора для контроля доступа к командам ввода-вывода в зависимости от привилегированности задачи
    ntфлажок вложенности задачи
    (Nested Task)
    14 Используется в защищенном режиме работы микропроцессора для фиксации того факта, что одна задача вложена в другую
    Таблица 2. Системные флаги
    Мнемоника флагаФлагНомер бита в eflagsСодержание и назначение
    tfФлаг трассировки
    (Trace Flag)
    8 Предназначен для организации пошаговой работы микропроцессора.
    1 — микропроцессор генерирует прерывание с номером 1 после выполнения каждой машинной команды. Может использоваться при отладке программ, в частности отладчиками;
    0 — обычная работа
    ifФлаг прерывания
    (Interrupt enable Flag)
    9 Предназначен для разрешения или запрещения (маскирования) аппаратных прерываний (прерываний по входу INTR).
    1 — аппаратные прерывания разрешены;
    0 — аппаратные прерывания запрещены
    rfФлаг возобновления
    (Resume Flag)
    16 Используется при обработке прерываний от регистров отладки.
    vmФлаг виртуального
    (Virtual 8086 Mode)
    17 Признак работы микропроцессора в режиме виртуального 8086.
    1 — процессор работает в режиме виртуального 8086;
    0 — процессор работает в реальном или защищенном режиме
    acФлаг контроля выравнивания
    (Alignment Check)
    18 Предназначен для разрешения контроля выравнивания при обращениях к памяти. Используется совместно с битом am в системном регистре cr0. К примеру, Pentium разрешает размещать команды и данные с любого адреса. Если требуется контролировать выравнивание данных и команд по адресам кратным 2 или 4, то установка данных битов приведет к тому, что все обращения по некратным адресам будут возбуждать исключительную ситуацию

    eip/ip (Instraction Pointer register) — регистр-указатель команд.
    Регистр eip/ip имеет разрядность 32/16 бит и содержит смещение следующей подлежащей выполнению команды относительно содержимого сегментного регистра cs в текущем сегменте команд. Этот регистр непосредственно недоступен программисту, но загрузка и изменение его значения производятся различными командами управления, к которым относятся команды условных и безусловных переходов, вызова процедур и возврата из процедур. Возникновение прерываний также приводит к модификации регистра eip/ip.

    Системные регистры микропроцессора

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

    Системные регистры можно разделить на три группы: четыре регистра управления; четыре регистра системных адресов; восемь регистров отладки.

    Регистры управления

    В группу регистров управления входят 4 регистра:
    cr0, cr1, cr2, cr3.

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

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

    Регистр cr0 содержит системные флаги, управляющие режимами работы микропроцессора и отражающие его состояние глобально, независимо от конкретных выполняющихся задач.
    Назначение системных флагов: pe (Protect Enable), бит 0 — разрешение защищенного режима работы.
    Состояние этого флага показывает, в каком из двух режимов — реальном (pe=0) или защищенном (pe=1) — работает микропроцессор в данный момент времени. mp (Math Present), бит 1 — наличие сопроцессора. Всегда 1. ts (Task Switched), бит 3 — переключение задач.
    Процессор автоматически устанавливает этот бит при переключении на выполнение другой задачи. am (Aligment Mask), бит 18 — маска выравнивания.
    Этот бит разрешает (am = 1) или запрещает (am = 0) контроль выравнивания. cd (Cache Disable), бит 30, — запрещение кэш-памяти.
    С помощью этого бита можно запретить (cd = 1) или разрешить (cd = 0) использование внутренней кэш-памяти (кэш-памяти первого уровня). pg (PaGing), бит 31, — разрешение (pg = 1) или запрещение (pg = 0) страничного преобразования.
    Флаг используется при страничной модели организации памяти.

    Регистр cr2 используется при страничной организации оперативной памяти для регистрации ситуации, когда текущая команда обратилась по адресу, содержащемуся в странице памяти, отсутствующей в данный момент времени в памяти.
    В такой ситуации в микропроцессоре возникает исключительная ситуация с номером 14, и линейный 32-битный адрес команды, вызвавшей это исключение, записывается в регистр cr2. Имея эту информацию, обработчик исключения 14 определяет нужную страницу, осуществляет ее подкачку в память и возобновляет нормальную работу программы;

    Регистр cr3 также используется при страничной организации памяти.
    Это так называемый регистр каталога страниц первого уровня. Он содержит 20-битный физический базовый адрес каталога страниц текущей задачи. Этот каталог содержит 1024 32-битных дескриптора, каждый из которых содержит адрес таблицы страниц второго уровня. В свою очередь каждая из таблиц страниц второго уровня содержит 1024 32-битных дескриптора, адресующих страничные кадры в памяти. Размер страничного кадра — 4 Кбайт.

    Регистры системных адресов

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

    При работе в защищенном режиме микропроцессора адресное пространство делится на: глобальное — общее для всех задач; локальное — отдельное для каждой задачи. Этим разделением и объясняется присутствие в архитектуре микропроцессора следующих системных регистров: регистра таблицы глобальных дескрипторов gdtr (Global Descriptor Table Register) имеющего размер 48 бит и содержащего 32-битовый (биты 16—47) базовый адрес глобальной дескрипторной таблицы GDT и 16-битовое (биты 0—15) значение предела, представляющее собой размер в байтах таблицы GDT; регистра таблицы локальных дескрипторов ldtr (Local Descriptor Table Register) имеющего размер 16 бит и содержащего так называемый селектор дескриптора локальной дескрипторной таблицы LDT. Этот селектор является указателем в таблице GDT, который и описывает сегмент, содержащий локальную дескрипторную таблицу LDT; регистра таблицы дескрипторов прерываний idtr (Interrupt Descriptor Table Register) имеющего размер 48 бит и содержащего 32-битовый (биты 16–47) базовый адрес дескрипторной таблицы прерываний IDT и 16-битовое (биты 0—15) значение предела, представляющее собой размер в байтах таблицы IDT; 16-битового регистра задачи tr (Task Register), который подобно регистру ldtr, содержит селектор, то есть указатель на дескриптор в таблице GDT. Этот дескриптор описывает текущий сегмент состояния задачи (TSS — Task Segment Status). Этот сегмент создается для каждой задачи в системе, имеет жестко регламентированную структуру и содержит контекст (текущее состояние) задачи. Основное назначение сегментов TSS — сохранять текущее состояние задачи в момент переключения на другую задачу.

    Регистры отладки

    Это очень интересная группа регистров, предназначенных для аппаратной отладки. Средства аппаратной отладки впервые появились в микропроцессоре i486. Аппаратно микропроцессор содержит восемь регистров отладки, но реально из них используются только 6.

    Регистры dr0, dr1, dr2, dr3 имеют разрядность 32 бит и предназначены для задания линейных адресов четырех точек прерывания. Используемый при этом механизм следующий: любой формируемый текущей программой адрес сравнивается с адресами в регистрах dr0...dr3, и при совпадении генерируется исключение отладки с номером 1.

    Регистр dr6 называется регистром состояния отладки. Биты этого регистра устанавливаются в соответствии с причинами, которые вызвали возникновение последнего исключения с номером 1.

    Перечислим эти биты и их назначение: b0 — если этот бит установлен в 1, то последнее исключение (прерывание) возникло в результате достижения контрольной точки, определенной в регистре dr0; b1 — аналогично b0, но для контрольной точки в регистре dr1; b2 — аналогично b0, но для контрольной точки в регистре dr2; b3 — аналогично b0, но для контрольной точки в регистре dr3; bd (бит 13) — служит для защиты регистров отладки; bs (бит 14) — устанавливается в 1, если исключение 1 было вызвано состоянием флага tf = 1 в регистре eflags; bt (бит 15) устанавливается в 1, если исключение 1 было вызвано переключением на задачу с установленным битом ловушки в TSS t = 1. Все остальные биты в этом регистре заполняются нулями. Обработчик исключения 1 по содержимому dr6 должен определить причину, по которой произошло исключение, и выполнить необходимые действия.

    Регистр dr7 называется регистром управления отладкой. В нем для каждого из четырех регистров контрольных точек отладки имеются поля, с помощью которых можно уточнить следующие условия, при которых следует сгенерировать прерывание: место регистрации контрольной точки — только в текущей задаче или в любой задаче. Эти биты занимают младшие восемь бит регистра dr7 (по два бита на каждую контрольную точку (фактически точку прерывания), задаваемую регистрами dr0, dr1, dr2, dr3 соответственно).
    Первый бит из каждой пары — это так называемое локальное разрешение; его установка говорит о том, что точка прерывания действует если она находится в пределах адресного пространства текущей задачи.
    Второй бит в каждой паре определяет глобальное разрешение, которое говорит о том, что данная контрольная точка действует в пределах адресных пространств всех задач, находящихся в системе; тип доступа, по которому инициируется прерывание: только при выборке команды, при записи или при записи/чтении данных. Биты, определяющие подобную природу возникновения прерывания, локализуются в старшей части данного регистра.

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

    Массивы

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

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

    Описание и инициализация массива в программе

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

    ;массив из 5 элементов.Размер каждого
    элемента 4 байта:
    mas dd 1,2,3,4,5
    
    Используя оператор повторения dup. К примеру:
    ;массив из 5 нулевых элементов.
    ;Размер каждого элемента 2 байта:
    mas dw 5 dup (0)
    

    Такой способ определения используется для резервирования памяти с целью размещения и инициализации элементов массива. Используя директивы label и rept. Пара этих директив может облегчить описание больших массивов в памяти и повысить наглядность такого описания. Директива rept относится к макросредствам языка ассемблера и вызывает повторение указанное число раз строк, заключенных между директивой и строкой endm. К примеру, определим массив байт в области памяти, обозначенной идентификатором mas_b. В данном случае директива label определяет символическое имя mas_b, аналогично тому, как это делают директивы резервирования и инициализации памяти. Достоинство директивы label в том, что она не резервирует память, а лишь определяет характеристики объекта. В данном случае объект — это ячейка памяти. Используя несколько директив label, записанных одна за другой, можно присвоить одной и той же области памяти разные имена и разный тип, что и сделано в следующем фрагменте:
    ...
    n=0
    ...
    mas_b label byte
    mas_w label word
    rept 4
    dw 0f1f0h
    endm
    

    В результате в памяти будет создана последовательность из четырех слов f1f0. Эту последовательность можно трактовать как массив байт или слов в зависимости от того, какое имя области мы будем использовать в программе — mas_b или mas_w. Использование цикла для инициализации значениями области памяти, которую можно будет впоследствии трактовать как массив.
    Посмотрим на примере листинга 2, каким образом это делается.

    Листинг 2 Инициализация массива в цикле
    ;prg_12_1.asm
    MASM
    MODEL small
    STACK 256
    .data
    mes db 0ah,0dh,'Массив- ','$'
    mas db 10 dup (?) ;исходный массив
    i db 0
    .code
    main:
    mov ax,@data
    mov ds,ax
    xor ax,ax ;обнуление ax
    mov cx,10 ;значение счетчика цикла в cx
    mov si,0 ;индекс начального элемента в cx
    go: ;цикл инициализации
    mov bh,i ;i в bh
    mov mas[si],bh ;запись в массив i
    inc i ;инкремент i
    inc si ;продвижение к следующему элементу массива
    loop go ;повторить цикл
    ;вывод на экран получившегося массива
    mov cx,10
    mov si,0
    mov ah,09h
    lea dx,mes
    int 21h
    show:
    mov ah,02h ;функция вывода значения из al на экран
    mov dl,mas[si]
    add dl,30h ;преобразование числа в символ
    int 21h
    inc si
    loop show
    exit:
    mov ax,4c00h ;стандартный выход
    int 21h
    end main ;конец программы
    

    Доступ к элементам массива

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

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

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

    mas dw 0,1,2,3,4,5
    

    Пусть эта последовательность чисел трактуется как одномерный массив. Размерность каждого элемента определяется директивой dw, то есть она равна 2 байта. Чтобы получить доступ к третьему элементу, нужно к адресу массива прибавить 6. Нумерация элементов массива в ассемблере начинается с нуля.
    То есть в нашем случае речь, фактически, идет о 4-м элементе массива — 3, но об этом знает только программист; микропроцессору в данном случае все равно — ему нужен только адрес.

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

    база + (индекс*размер элемента)

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

    mas dw 0,1,2,3,4,5
    ...
    mov si,4
    ;поместить 3-й элемент массива mas в регистр ax:
    mov ax,mas[si]
    
    базовая индексная адресация со смещением — режим адресации, при котором эффективный адрес формируется максимум из трех компонентов: постоянного (необязательный компонент), в качестве которой может выступать прямой адрес массива в виде имени идентификатора, обозначающего начало массива, или непосредственное значение; переменного (базового) — указанием имени базового регистра; переменного (индексного) — указанием имени индексного регистра.

    Этот вид адресации удобно использовать при обработке двухмерных массивов. Пример использования этой адресации мы рассмотрим далее при изучении особенностей работы с двухмерными массивами.

    Напомним, что в качестве базового регистра может использоваться любой из восьми регистров общего назначения. В качестве индексного регистра также можно использовать любой регистр общего назначения, за исключением esp/sp.

    Микропроцессор позволяет масштабировать индекс. Это означает, что если указать после имени индексного регистра знак умножения “*” с последующей цифрой 2, 4 или 8, то содержимое индексного регистра будет умножаться на 2, 4 или 8, то есть масштабироваться.

    Применение масштабирования облегчает работу с массивами, которые имеют размер элементов, равный 2, 4 или 8 байт, так как микропроцессор сам производит коррекцию индекса для получения адреса очередного элемента массива. Нам нужно лишь загрузить в индексный регистр значение требуемого индекса (считая от 0). Кстати сказать, возможность масштабирования появилась в микропроцессорах Intel, начиная с модели i486. По этой причине в рассматриваемом здесь примере программы стоит директива .486. Ее назначение, как и ранее использовавшейся директивы .386, в том, чтобы указать ассемблеру при формировании машинных команд на необходимость учета и использования дополнительных возможностей системы команд новых моделей микропроцессоров.

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

    Листинг 3. Просмотр массива слов с использованием
    масштабирования
    ;prg_12_2.asm
    MASM
    MODEL small
    STACK 256
    .data ;начало сегмента данных
    ;тексты сообщений:
    mes1 db 'не равен 0!$',0ah,0dh
    mes2 db 'равен 0!$',0ah,0dh
    mes3 db 0ah,0dh,'Элемент $'
    mas dw 2,7,0,0,1,9,3,6,0,8 ;исходный массив
    .code
    .486 ;это обязательно
    main:
    mov ax,@data
    mov ds,ax ;связка ds с сегментом данных
    xor ax,ax ;обнуление ax
    prepare:
    mov cx,10 ;значение счетчика цикла в cx
    mov esi,0 ;индекс в esi
    compare:
    mov dx,mas[esi*2] ;первый элемент массива в dx
    cmp dx,0 ;сравнение dx c 0
    je equal ;переход, если равно
    not_equal: ;не равно
    mov ah,09h ;вывод сообщения на экран
    lea dx,mes3
    int 21h
    mov ah,02h ;вывод номера элемента массива на экран
    mov dx,si
    add dl,30h
    int 21h
    mov ah,09h
    lea dx,mes1
    int 21h
    inc esi ;на следующий элемент
    dec cx ;условие для выхода из цикла
    jcxz exit ;cx=0? Если да — на выход
    jmp compare ;нет — повторить цикл
    equal: ;равно 0
    mov ah,09h ;вывод сообщения mes3 на экран
    lea dx,mes3
    int 21h
    mov ah,02h
    mov dx,si
    add dl,30h
    int 21h
    mov ah,09h ;вывод сообщения mes2 на экран
    lea dx,mes2
    int 21h
    inc esi ;на следующий элемент
    dec cx ;все элементы обработаны?
    jcxz exit
    jmp compare
    exit:
    mov ax,4c00h ;стандартный выход
    int 21h
    end main ;конец программы
    

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

    ;переслать байт из области данных, адрес
    которой находится в регистре ebx:
    mov al,[ebx]
    
    Если для задания адреса в команде используется прямая адресация (в виде идентификатора) в сочетании с одним регистром, то речь идет об индексной адресации. Регистр считается индексным, и поэтому можно использовать масштабирование для получения адреса нужного элемента массива:
    add eax,mas[ebx*4]
    ;сложить содержимое eax с двойным словом в памяти
    ;по адресу mas + (ebx)*4
    
    Если для описания адреса используются два регистра, то речь идет о базово-индексной адресации. Левый регистр рассматривается как базовый, а правый — как индексный. В общем случае это не принципиально, но если мы используем масштабирование с одним из регистров, то он всегда является индексным. Но лучше придерживаться определенных соглашений.
    Помните, что применение регистров ebp/bp и esp/sp по умолчанию подразумевает, что сегментная составляющая адреса находится в регистре ss.

    Заметим, что базово-индексную адресацию не возбраняется сочетать с прямой адресацией или указанием непосредственного значения. Адрес тогда будет формироваться как сумма всех компонентов.

    К примеру:

    mov ax,mas[ebx][ecx*2]
    ;адрес операнда равен [mas+(ebx)+(ecx)*2]
    ...
    sub dx,[ebx+8][ecx*4]
    ;адрес операнда равен [(ebx)+8+(ecx)*4]
    

    Но имейте в виду, что масштабирование эффективно лишь тогда, когда размерность элементов массива равна 2, 4 или 8 байт. Если же размерность элементов другая, то организовывать обращение к элементам массива нужно обычным способом, как описано ранее.

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

    Листинг 4. Обработка массива элементов с нечетной длиной
    ;prg_11_3.asm
    MASM
    MODEL small ;модель памяти
    STACK 256 ;размер стека
    .data ;начало сегмента данных
    N=5 ;количество элементов массива
    mas db 5 dup (3 dup (0))
    .code ;сегмент кода
    main: ;точка входа в программу
    mov ax,@data
    mov ds,ax
    xor ax,ax ;обнуление ax
    mov si,0 ;0 в si
    mov cx,N ;N в cx
    go:
    mov dl,mas[si] ;первый байт поля в dl
    inc dl ;увеличение dl на 1 (по условию)
    mov mas[si],dl ;заслать обратно в массив
    add si,3 ;сдвиг на следующий элемент массива
    loop go ;повтор цикла
    mov si,0 ;подготовка к выводу на экран
    mov cx,N
    show: ;вывод на экран содержимого
    ;первых байт полей
    mov dl,mas[si]
    add dl,30h
    mov ah,02h
    int 21h
    loop show
    exit:
    mov ax,4c00h ;стандартный выход
    int 21h
    end main ;конец программы
    

    Двухмерные массивы

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

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

    Если последовательность однотипных элементов в памяти трактуется как двухмерный массив, расположенный по строкам, то адрес элемента (i, j) вычисляется по формуле

    (база + количество_элементов_в_строке * размер_элемента * i+j)

    Здесь i = 0...n–1 указывает номер строки, а j = 0...m–1 указывает номер столбца.

    Например, пусть имеется массив чисел (размером в 1 байт) mas(i, j) с размерностью 4 на 4
    (i= 0...3, j = 0...3):

    23 04 05 67
    05 06 07 99
    67 08 09 23
    87 09 00 08
    

    В памяти элементы этого массива будут расположены в следующей последовательности:

    23 04 05 67 05 06 07 99 67 08 09 23 87 09 00 08

    Если мы хотим трактовать эту последовательность как двухмерный массив, приведенный выше, и извлечь, например, элемент
    mas(2, 3) = 23, то проведя нехитрый подсчет, убедимся в правильности наших рассуждений:

    Эффективный адрес mas(2, 3) = mas + 4 * 1 * 2 + 3 = mas + 11

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

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

    mov ax,mas[ebx][esi]
    
    сочетание двух индексных регистров, один из которых является и базовым и индексным одновременно, а другой — только индексным:
    mov ax,[ebx][esi]
    

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

    ;Фрагмент программы выборки элемента
    ;массива mas(2,3) и его обнуления
    .data
    mas db
    23,4,5,67,5,6,7,99,67,8,9,23,87,9,0,8
    i=2
    j=3
    .code
    ...
    mov si,4*1*i
    mov di,j
    mov al,mas[si][di] ;в al элемент mas(2,3)
    ...
    

    В качестве законченного примера рассмотрим программу поиска элемента в двухмерном массиве чисел (листинг 5). Элементы массива заданы статически.

    Листинг 5. Поиск элемента в двухмерном массиве
    ;prg_11_4.asm
    MASM
    MODEL small
    STACK 256
    .data
    ;матрица размером 2x5 — если ее не инициализировать,
    ;то для наглядности она может быть описана так:
    ;array dw 2 DUP (5 DUP (?))
    ;но мы ее инициализируем:
    array dw 1,2,3,4,5,6,7,3,9,0
    ;логически это будет выглядеть так:
    ;array= {1 2}
    ; {3 4}
    ; {5 6}
    ; {7 3}
    ; {9 0}
    elem dw 3 ;элемент для поиска
    failed db 0ah,0dh,'Нет такого элемента в массиве!','$'
    success db 0ah,0dh,'Такой элемент в массиве присутствует ','$'
    foundtime db ? ;количество найденных элементов
    fnd db ' раз(а)',0ah,0dh,'$'
    .code
    main:
    mov ax,@data
    mov ds,ax
    xor ax,ax
    mov si,0 ;si=столбцы в матрице
    mov bx,0 ;bx=строки в матрице
    mov cx,5 ;число для внешнего цикла (по строкам)
    external: ;внешний цикл по строкам
    mov ax,array[bx][si] ;в ax первый элемент матрицы
    push cx ;сохранение в стеке счётчика внешнего цикла
    mov cx,2 ;число для внутреннего цикла (по столбцам)
    mov si,0
    iternal: ;внутренний цикл по строкам
    inc si ;передвижение на следующий элемент в строке
    ;сравниваем содержимое текущего элемента в ax с искомым элементом:
    cmp ax,elem
    ;если текущий совпал с искомым, то переход на here для обработки,
    ;иначе цикл продолжения поиска
    je here
    ;иначе — цикл по строке cx=2 раз
    loop iternal
    here:
    jcxz move_next ;просмотрели строку?
    inc foundtime ;иначе увеличиваем счётчик совпавших
    move_next: ;продвижение в матрице
    pop cx ;восстанавливаем CX из стека (5)
    add bx,1 ;передвигаемся на следующую строку
    loop external ;цикл (внешний)
    cmp foundtime,0h ;сравнение числа совпавших с 0
    ja eql ;если больше 0, то переход
    not_equal: ;нет элементов, совпавших с искомым
    mov ah,09h ;вывод сообщения на экран
    mov dx,offset failed
    int 21h
    jmp exit ;на выход
    eql: ;есть элементы, совпавшие с искомым
    mov ah,09h ;вывод сообщений на экран
    mov dx,offset success
    int 21h
    mov ah,02h
    mov dl,foundtime
    add dl,30h
    int 21h
    mov ah,09h
    mov dx,offset fnd
    int 21h
    exit: ;выход
    mov ax,4c00h ;стандартное завершение программы
    int 21h
    end main ;конец программы
    

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

    Типовые операции с массивами

    Для демонстрации основных приемов работы с массивами лучше всего подходят программы поиска или сортировки.

    Рассмотрим одну такую программу, выполняющую сортировку массива по возрастанию (листинг 6).

    Листинг 6. Сортировка массива
    <1> ;prg_12_5.asm
    <2> MASM
    <3> MODEL small
    <4> STACK 256
    <5> .data
    <6> mes1 db 0ah,0dh,'Исходный массив — $',0ah,0dh
    <7> ;некоторые сообщения
    <8> mes2 db 0ah,0dh,'Отсортированный массив — $',0ah,0dh
    <9> n equ 9 ;количество элементов в массиве, считая с 0
    <10> mas dw 2,7,4,0,1,9,3,6,5,8 ;исходный массив
    <11> tmp dw 0 ;переменные для работы с массивом
    <12> i dw 0
    <13> j dw 0
    <14> .code
    <15> main:
    <16> mov ax,@data
    <17> mov ds,ax
    <18> xor ax,ax
    <19> ;вывод на экран исходного массива
    <20> mov ah,09h
    <21> lea dx,mes1
    <22> int 21h ;вывод сообщения mes1
    <23> mov cx,10
    <24> mov si,0
    <25> show_primary: ;вывод значения элементов
    <26> ;исходного массива на экран
    <27> mov dx,mas[si]
    <28> add dl,30h
    <29> mov ah,02h
    <30> int 21h
    <31> add si,2
    <32> loop show_primary
    <33>
    <34> ;строки 40-85 программы эквивалентны следующему коду на языке С:
    <35> ;for (i=0;i<9;i++)
    <36> ; for (j=9;j>i;j--)
    <37> ; if (mas[i]>mas[j])
    <38> ; {tmp=mas[i];
    <39> ; mas[i]=mas[j];
    <40> ; mas[j]=tmp;}
    <41> mov i,0 ;инициализация i
    <42> ;внутренний цикл по j
    <43> internal:
    <44> mov j,9 ;инициализация j
    <45> jmp cycl_j ;переход на тело цикла
    <46> exchange:
    <47> mov bx,i ;bx=i
    <48> shl bx,1
    <49> mov ax,mas[bx] ;ax=mas[i]
    <50> mov bx,j ;bx=j
    <51> shl bx,1
    <52> cmp ax,mas[bx] ;mas[i] ? mas[j] — сравнение элементов
    <53> jle lesser ;если mas[i] меньше, то обмен не нужен и
    ;переход на продвижение далее по массиву
    <54> ;иначе tmp=mas[i], mas[i]=mas[j], mas[j]=tmp:
    <55> ;tmp=mas[i]
    <56> mov bx,i ;bx=i
    <57> shl bx,1 ;умножаем на 2, так как элементы — слова
    <58> mov tmp,ax ;tmp=mas[i]
    <59>
    <60> ;mas[i]=mas[j]
    <61> mov bx,j ;bx=j
    <62> shl bx,1 ;умножаем на 2, так как элементы — слова
    <63> mov ax,mas[bx] ;ax=mas[j]
    <64> mov bx,i ;bx=i
    <65> shl bx,1 ;умножаем на 2, так как элементы — слова
    <66> mov mas[bx],ax ;mas[i]=mas[j]
    <67>
    <68> ;mas[j]=tmp
    <69> mov bx,j ;bx=j
    <70> shl bx,1 ;умножаем на 2, так как элементы — слова
    <71> mov ax,tmp ;ax=tmp
    <72> mov mas[bx],ax ;mas[j]=tmp
    <73> lesser: ;продвижение далее по массиву во внутреннем цикле
    <74> dec j ;j--
    <75>;тело цикла по j
    <76> cycl_j:
    <77> mov ax,j ;ax=j
    <78> cmp ax,i ;сравнить j ? i
    <79> jg exchange ;если j>i, то переход на обмен
    <80> ;иначе на внешний цикл по i
    <81> inc i ;i++
    <82> cmp i,n ;сравнить i ? n — прошли до конца массива
    <83> jl internal ;если i
    <85> ;вывод отсортированного массива
    <86> mov ah,09h
    <87> lea dx,mes2
    <88> int 21h
    <89> prepare:
    <90> mov cx,10
    <91> mov si,0
    <92> show: ;вывод значения элемента на экран
    <93> mov dx,mas[si]
    <94> add dl,30h
    <95> mov ah,02h
    <96> int 21h
    <97> add si,2
    <98> loop show
    <99> exit:
    <100> mov ax,4c00h ;стандартный выход
    <101> int 21h
    <102> end main ;конец программы
    

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

    Структуры

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

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

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

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

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

    Описание шаблона структуры

    Описание шаблона структуры имеет следующий синтаксис:

    имя_структуры STRUC
    <описание полей>
    имя_структуры ENDS
    

    Здесь <описание полей> представляет собой последовательность директив описания данных db, dw, dd, dq и dt.
    Их операнды определяют размер полей и, при необходимости, начальные значения. Этими значениями будут, возможно, инициализироваться соответствующие поля при определении структуры.

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

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

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

    worker struc ;информация о сотруднике
    nam db 30 dup (' ') ;фамилия, имя, отчество
    sex db 'м' ;пол, по умолчанию 'м' — мужской
    position db 30 dup (' ') ;должность
    age db 2 dup(‘ ’) ;возраст
    standing db 2 dup(‘ ’) ;стаж
    salary db 4 dup(‘ ’) ;оклад в рублях
    birthdate db 8 dup(‘ ’) ;дата рождения
    worker ends
    

    Определение данных с типом структуры

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

    [имя переменной] имя_структуры <[список значений]>
    

    Здесь: имя переменной — идентификатор переменной данного структурного типа.
    Задание имени переменной необязательно. Если его не указать, будет просто выделена область памяти размером в сумму длин всех элементов структуры. список значений — заключенный в угловые скобки список начальных значений элементов структуры, разделенных запятыми.
    Его задание также необязательно.
    Если список указан не полностью, то все поля структуры для данной переменной инициализируются значениями из шаблона, если таковые заданы.
    Допускается инициализация отдельных полей, но в этом случае пропущенные поля должны отделяться запятыми. Пропущенные поля будут инициализированы значениями из шаблона структуры. Если при определении новой переменной с типом данной структуры мы согласны со всеми значениями полей в ее шаблоне (то есть заданными по умолчанию), то нужно просто написать угловые скобки.
    К примеру: victor worker <>.

    Для примера определим несколько переменных с типом описанной выше структуры.

    data segment
    sotr1 worker <’Гурко Андрей Вячеславович’,,’художник’,’33’,‘15’,‘1800’,’26.01.64’<
    sotr2 worker <’Михайлова Наталья Геннадьевна’,’ж’,’программист’,’30’,’10’,’1680’,’27.10.58’<
    sotr3 worker <’Степанов Юрий Лонгинович’,,’художник’,’38’,’20’,’1750’,’01.01.58’<
    sotr4 worker <’Юрова Елена Александровна’,’ж’,’свяэист’,’32’,’2’,,’09.01.66’<
    sotr5 worker <> ;здесь все значения по умолчанию
    data ends
    

    Методы работы со структурой

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

    адресное_выражение.имя_поля_структуры
    

    Здесь: адресное_выражение — идентификатор переменной некоторого структурного типа или выражение в скобках в соответствии с указанными ниже синтаксическими правилами (рис. 1); имя_поля_структуры — имя поля из шаблона структуры.
    Это, на самом деле, тоже адрес, а точнее, смещение поля от начала структуры.

    Таким образом оператор "." (точка) вычисляет выражение

    (адресное_выражение) + (имя_поля_структуры)
    

    Рис. 5. Синтаксис адресного выражения в операторе обращения к полю структуры

    Продемонстрируем на примере определенной нами структуры worker некоторые приемы работы со структурами.
    К примеру, извлечь в ax значения поля с возрастом. Так как вряд ли возраст трудоспособного человека будет больше величины 99 лет, то после помещения содержимого этого символьного поля в регистр ax его будет удобно преобразовать в двоичное представление командой
    aad.
    Будьте внимательны, так как из-за принципа хранения данных “младший байт по младшему адресу” старшая цифра возраста будет помещена в al, а младшая — в ah.
    Для корректировки достаточно использовать команду xchg al,ah:

    mov ax,word ptr sotr1.age ;в al возраст sotr1
    xchg ah,al
    

    а можно и так:

    lea bx,sotr1
    mov ax,word ptr [bx].age
    xchg ah,al
    

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

    mas_sotr worker 10 dup (<>)
    

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

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

    worker struc
    ...
    worker ends
    ...
    mas_sotr worker 10 dup (<>)
    ...
    mov bx,type worker ;bx=77
    lea di,mas_sotr
    ;извлечь и вывести на экран пол всех сотрудников:
    mov cx,10
    cycl:
    mov al,[di].sex
    ...
    ;вывод на экран содержимого поля sex структуры worker
    add di,bx ;к следующей структуре в массиве mas_sort
    loop cycl
    

    Как выполнить копирование поля из одной структуры в соответствующее поле другой структуры? Или как выполнить копирование всей структуры? Давайте выполним копирование поля nam третьего сотрудника в поле nam пятого сотрудника:

    worker struc
    ...
    worker ends
    ...
    mas_sotr worker 10 dup (<>)
    ...
    mov bx,offset mas_sotr
    mov si,(type worker)*2 ;si=77*2
    add si,bx
    mov di,(type worker)*4 ;si=77*4
    add di,bx
    mov cx,30
    rep movsb
    


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

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

    Объединения

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

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

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

    имя_объединения UNION
    <описание полей>
    имя_объединения ENDS
    

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

    Листинг 7, который мы сейчас рассмотрим, примечателен тем, что кроме демонстрации использования собственно типа данных “объединение” в нем показывается возможность взаимного вложения структур и объединений.
    Постарайтесь внимательно отнестись к анализу этой программы. Основная идея здесь в том, что указатель на память, формируемый программой, может быть представлен в виде: 16-битного смещения; 32-битного смещения; пары из 16-битного смещения и 16-битной сегментной составляющей адреса; в виде пары из 32-битного смещения и 16-битного селектора.

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

    Листинг 7 Пример использования объединения
    masm
    model small
    stack 256
    .586P
    pnt struc ;структура pnt, содержащая вложенное объединение
    union ;описание вложенного в структуру объединения
    offs_16 dw ?
    offs_32 dd ?
    ends ;конец описания объединения
    segm dw ?
    ends ;конец описания структуры
    .data
    point union ;определение объединения, содержащего вложенную структуру
    off_16 dw ?
    off_32 dd ?
    point_16 pnt <>
    point_32 pnt <>
    point ends
    tst db "Строка для тестирования"
    adr_data point <> ;определение экземпляра объединения
    .code
    main:
    mov ax,@data
    mov ds,ax
    mov ax,seg tst
    ;записать адрес сегмента строки tst в поле структуры adr_data
    mov adr_data.point_16.segm,ax
    ;когда понадобится, можно извлечь значение из этого поля обратно, к примеру, в регистр bx:
    mov bx,adr_data.point_16.segm
    ;формируем смещение в поле структуры adr_data
    mov ax,offset tst ;смещение строки в ax
    mov adr_data.point_16.offs_16,ax
    ;аналогично, когда понадобится, можно извлечь значение из этого поля:
    mov bx,adr_data.point_16.offs_16
    exit:
    mov ax,4c00h
    int 21h
    end main
    

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

    Записи

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

    TASM предоставляет нам специальный тип данных, использование которого помогает решить проблему работы с битами более эффективно. Речь идет о специальном типе данных — записях.

    Запись — структурный тип данных, состоящий из фиксированного числа элементов длиной от одного до нескольких бит.
    При описании записи для каждого элемента указывается его длина в битах и, что необязательно, некоторое значение.
    Суммарный размер записи определяется суммой размеров ее полей и не может быть более 8, 16 или 32 бит.
    Если суммарный размер записи меньше указанных значений, то все поля записи “прижимаются” к младшим разрядам.

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

    Компилятор TASM, кроме стандартных средств обработки записей, поддерживает также и некоторые дополнительные возможности их обработки.

    Описание записи

    Описание шаблона записи имеет следующий синтаксис (рис. 6):

    имя_записи RECORD <описание элементов>
    

    Здесь:
    <описание элементов> представляет собой последовательность описаний отдельных элементов записи согласно синтаксической диаграмме (см. рис. 6):

    Рис. 6. Синтаксис описания шаблона записи

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

    Определение экземпляра записи

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

    Рис. 7. Синтаксис описания экземпляра записи

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

    Если инициализировать поля не требуется, то достаточно указать ? при определении экземпляра записи:

    ...
    iotest record
    i1:1,i2:2=11,i3:1,i4:2=11,i5:2=00
    ...
    flag iotest ?
    

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

    Если требуется частичная инициализация элементов, то они заключаются в угловые (< и >) или фигурные ({ и }) скобки.
    Различие здесь в том, что в угловых скобках элементы должны быть заданы в том же порядке, что и в определении записи. Если значение некоторого элемента совпадает с начальным, то его можно не указывать, но обязательно обозначить его запятой. Для последних элементов идущие подряд запятые можно опустить.
    К примеру, согласиться со значениями по умолчанию можно так:

    iotest record
    i1:1,i2:2=11,i3:1,i4:2=11,i5:2=00
    ...
    flag iotest <> ;согласились со значением по умолчанию
    

    Изменить значение поля i2 можно так:

    iotest record
    i1:1,i2:2=11,i3:1,i4:2=11,i5:2=00
    ...
    flag iotest <,10,> ; переопределили i2
    

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

    iotest record
    i1:1,i2:2=11,i3:1,i4:2=11,i5:2=00
    ...
    flag iotest {i2=10} ;переопределили i2, не обращая внимания на порядок
    ;следования других компонентов записи
    

    Работа с записями

    Как организовать работу с отдельными элементами записи? Обычные механизмы адресации здесь бессильны, так как они работают на уровне ячеек памяти, то есть байтов, а не отдельных битов. Здесь программисту нужно приложить некоторые усилия.
    Прежде всего для понимания проблемы нужно усвоить несколько моментов: Каждому имени элемента записи ассемблер присваивает числовое значение, равное количеству сдвигов вправо, которые нужно произвести для того, чтобы этот элемент оказался “прижатым” к началу ячейки. Это дает нам возможность локализовать его и работать с ним. Но для этого нужно знать длину элемента в битах. Сдвиг вправо производится с помощью команды сдвига
    shr. Ассемблер содержит оператор width, который позволяет узнать размер элемента записи в битах или полностью размер записи. Варианты применения оператора width:

    width имя_элемента_записи ;значением оператора будет размер элемента в битах.
    

    width имя_экземпляра_записи
    или
    width имя_типа_записи ;значением оператора будет размер всей записи в битах.
    
    mov al,width i2
    ...
    mov ax,width iotest
    
    Ассемблер содержит оператор mask, который позволяет локализовать биты нужного элемента записи. Эта локализация производится путем создания маски, размер которой совпадает с размером записи. В этой маске обнулены биты на всех позициях, за исключением тех, которые занимает элемент в записи. Сами действия по преобразованию элементов записи производятся с помощью логических команд.

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

    Выделение элемента записи:
    Поместить запись во временную память — регистр (8, 16 или 32-битный в зависимости от размера записи). Получить битовую маску, соответствующую элементу записи, с помощью оператора mask. Локализовать биты в регистре с помощью маски и команды and. Сдвинуть биты элемента к младшим разрядам регистра командой shr. Число разрядов для сдвига получить с использованием имени элемента записи.

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

    Работа с элементом записи:

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

    Помещение измененного элемента на его место в запись:
    Используя имя элемента записи в качестве счетчика сдвигов, сдвинуть влево биты элемента записи. Если вы не уверены в том, что разрядность результата преобразований не превысила исходную, можно выполнить “обрезание” лишних битов, используя команду and и маску элемента. Подготовить исходную запись к вставке измененного элемента путем обнуления битов в записи на месте этого элемента. Это можно сделать путем наложения командой and инвертированной маски элемента записи на исходную запись. С помощью команды or наложить значение в регистре на исходную запись.

    В качестве примера рассмотрим листинг 8, который обнуляет поле i2 в записи iotest.

    Листинг 8. Работа с полем записи
    ;prg_12_7.asm
    masm
    model small
    stack 256
    iotest record i1:1,i2:2=11,i3:1,i4:2=11,i5:2=00
    .data
    flag iotest <>
    .code
    main:
    mov ax,@data
    mov ds,ax
    mov al,mask i2
    shr al,i2 ;биты i2 в начале ax
    and al,0fch ;обнулили i2
    ;помещаем i2 на место
    shl al,i2
    mov bl,[flag]
    xor bl,mask i2 ;сбросили i2
    or bl,al ;наложили
    exit:
    mov ax,4c00h ;стандартный выход
    int 21h
    end main ;конец программы
    

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

    Записи: дополнительные возможности обработки

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

    Для установки значения некоторого поля записи используется команда setfield с синтаксисом:

    setfield имя_элемента_записи назначение,регистр_источник
    

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

    getfield имя_элемента_записи регистр_назначение, источник
    

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

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

    Действие команды getfield обратно setfield. В качестве операнда источник может быть указан либо регистр либо адрес памяти.
    В регистр, указанный операндом регистр_назначение, помещается результат работы команды — значение элемента записи.
    Интересная особенность связана с регистр_назначение. Команда getfield всегда использует 16-битный регистр, даже если вы укажете в этой команде имя 8-битного регистра.

    В качестве примера применения команд setfield и getfield рассмотрим листинг 9.

    Листинг 9. Работа с полями записи
    ;prg_12_8.asm
    masm
    model small
    stack 256
    iotest record
    i1:1,i2:2=11,i3:1,i4:2=11,i5:2=00
    .data
    flag iotest <>
    .code
    main:
    mov ax,@data
    mov ds,ax
    mov al,flag
    mov bl,3
    setfield i5 al,bl
    xor bl,bl
    getfield i5 bl,al
    mov bl,1
    setfield i4 al,bl
    setfield i5 al,bl
    exit:
    mov ax,4c00h ;стандартный выход
    int 21h
    end main ;конец программы
    

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

    В заключение хотелось бы привести еще один пример использования записей.
    Это описание регистра eflags. Для удобства мы разбили это описание на три части: eflags_1_7 — младший байт eflags/flags; eflags_8_15 — второй байт eflags/flags; eflags_h — старшая половина eflags.

    eflags_l_7 record
    sf7:1=0,zf6:1=0,c5:1=0,af4:1=0,c3:1=0,pf2:1=0,c1:=1,cf0:1=0
    eflags_l_15 record
    c15:1=0,nt14:1=0,iopl:2=0,of11:1=0,df10:1=0,if9:1=1,tf8:1=0
    eflags_h record
    c:13=0,ac18:1=0,vm17:1=0,rf16:1=0
    

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


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

    Директивы этой группы предназначены для управления видом файла листинга. Все директивы являются парными — это означает, что если одна директива что-то разрешает, то другая, наоборот, запрещает. Рассмотрим назначение этих пар директив.

    %LIST и %NOLIST (.LIST и .XLIST)
    Директивы .LIST или %LIST определяют необходимость вывода в файл листинга всех строк исходного кода. Эти директивы подразумеваются по умолчанию.
    Для запрета вывода в файл листинга всех строк исходного кода необходимо использовать директивы .XLIST или %NOLIST. В тексте программы их можно применять произвольное количество раз, при этом очередная директива отменяет действие предыдущей.

    %CTLS и %NOCTLS
    Если предыдущие директивы влияют на полноту представления исходного кода в целом, то директивы %CTLS и %NOCTLS управляют выводом в файл листинга самих директив управления листингом.

    %SYMS и %NOSYMS Эти директивы определяют, включать (%SYMS) или не включать (%NOSYMS) в файл листинга таблицу идентификаторов. 

    Директивы вывода текста включаемых файлов

    %INCL и %NOINCL
    Эти директивы позволяют регулировать включение в файл листинга текста включаемых файлов (по директиве INCLUDE). По умолчанию включаемые файлы записываются в файл листинга. Директива %NOINCL запрещает вывод в файл листинга всех последующих включаемых файлов, пока вывод снова не будет разрешен директивой %INCL. 

    Директивы вывода блоков условного ассемблирования

    %CONDS и %NOCONDS (.LFCOND и .SFCONDS)
    Для исследования исходного текста программы, содержащего директивы условной компиляции, удобно использовать директивы, регулирующие включение блоков условной компиляции в листинг программы.
    Директива %CONDS (.LFCOND) заставляет TASM выводить в файл листинга все операторы условных блоков. При этом в файл листинга выводятся все блоки, в том числе с условием false. Директива %NOCONDS (.SFCONDS) запрещает вывод в файл листинга блоков условного ассемблирования с условием false.
    Директива .TFCOND переключает режимы вывода %CONDS (.LFCOND) и %NOCONDS (.SFCONDS). Эту директиву можно использовать как отдельно, так и совместно с директивами .LFCOND и .SFCONDS.
    Первая директива .TFCOND, которую обнаруживает TASM, разрешает вывод в листинг всех блоков условного ассемблирования.
    Следующая директива .TFCOND будет запрещать вывод этих блоков. С директивой .TFCOND можно использовать параметр командной строки транслятора TASM ???????????????????????????????????? /X: согласно ему блоки условного ассемблирования будут сначала выводиться в листинг, но первая же директива .TFCOND запретит их вывод.

    Директивы вывода макрорасширений

    %MACS (.LALL) и %NOMACS (.SALL)
    Аналогично директивам вывода блоков условной компиляции при отладке программы удобно регулировать полноту информации о применяемых макрокомандах. По умолчанию транслятор включает макрорасширения в файл листинга. Можно запретить вывод макрорасширений в файл листинга, что удобно на некоторых стадиях отладки. Директива %MACS (.LALL) разрешает вывод в листинг всех макрорасширений. Директивы %NOMACS (.SALL) запрещает вывод всех операторов макрорасширения в файл листинга. В режиме MASM можно использовать директиву .XALL, позволяющую выводить в листинг только те макрорасширения, которые генерируют код или данные.

    Директивы вывода листинга перекрестных ссылок

    Приведенные выше директивы %SYMS и %NOSYMS регулировали вывод в листинг таблицы идентификаторов, в которой приводится информация о метках, группах и сегментах, но там не сообщается, где они определены и где используются. Информация в таблице перекрестных ссылок исправляет этот недостаток. Она облегчает поиск меток и полезна для отладки программы. В приложении 1 приведена опция командной строки TASM /c для получения таблицы перекрестных ссылок. Но действие этой опции распространяется на весь исходный файл, что может быть не совсем удобным. Поэтому TASM дополнительно предоставляет директивы для создания таблиц перекрестных ссылок только для отдельных частей исходного кода. Директивы %CREF (.CREF) и %NOCREF (.XCREF) соответственно разрешают и запрещают сбор информации о перекрестных ссылках, начиная с точки, где они были определены. При этом директивы %NOCREF (.XCREF) позволяют выборочно запрещать сбор информации о перекрестных ссылках для определенных идентификаторов в программе. Эти директивы имеют следующий синтаксис: %NOCREF (.XCREF) [идентификатор, ...]

    Если в директиве %NOCREF (.XCREF) не указать идентификатор, то вывод перекрестных ссылок запрещается полностью, если указать некоторые идентификаторы, то информация не будет собираться только для этих идентификаторов.

    Директивы изменения формата листинга

    Директивы этой группы позволяют управлять форматом файла листинга.

    .PAGE
    Директива .PAGE задает высоту и ширину страницы файла листинга и начинает его новую страницу. Она имеет следующий синтаксис: PAGE [число_строк][,число_столбцов] PAGE +

    Здесь: число_строк задает число строк, выводимых на странице листинга; число столбцов находится в диапазоне 59...255 и задает число столбцов на странице. Если опустить один из этих параметров, то текущая установка данного параметра останется без изменений. Для изменения только числа столбцов необходимо указать перед этим параметром запятую. С помощью директивы .PAGE можно разбивать листинг на разделы, в пределах которых нумерация начинается с нуля. Так, при указании после директивы .PAGE символа “+” начинается новая страница, номер раздела увеличивается, а номер страницы снова устанавливается в 1. Если использовать директиву .PAGE без аргументов, то листинг возобновляется с новой страницы без изменения номера раздела.

    %PAGESIZE (.PAGESIZE)
    Директива %PAGESIZE работает так же, как и директива .PAGE, но, в отличие от последней, она не начинает новую страницу, а лишь определяет ее параметры: %PAGESIZE [число_строк][,число_столбцов]

    %NEWPAGE
    Директива %NEWPAGE работает аналогично директиве .PAGE без аргументов. Строки исходного текста после директивы %NEWPAGE будут начинаться с новой страницы.

    %BIN
    Директива %BIN устанавливает длину поля объектного кода в файле листинга. Ее синтаксис: %BIN размер

    Здесь размер — некоторая константа.
    По умолчанию поле объектного кода занимает в файле листинга до 20 позиций.

    %DEPTH
    Директива %DEPTH устанавливает размер поля глубины в файле листинга. Ее синтаксис: %DEPTH размер

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

    %LINENUM
    Директива %LINENUM позволяет задать размер поля занимаемого номерами строк в файле листинга: %LINENUM размер

    По умолчанию под номер строки отводятся четыре столбца.

    %TRUNC и %NOTRUNC
    Директивы %TRUNC и %NOTRUNC предназначены для усечения длинных полей листинга. Их синтаксис: %TRUNC и %NOTRUNC

    Если некоторая строка исходного кода получается слишком длинной, то она автоматически усекается. Если возникает необходимость увидеть всю генерируемую строку, то можно использовать директиву %NOTRUNC, действие которой будет заключаться в том, что слишком длинная строка будет переноситься на следующую строку. Для включения режима усечения нужно использовать директиву %TRUNC. Такие переключения можно осуществлять неограниченное количество раз.

    %PCNT
    Директива %PCNT задает размер поля “сегмент:смещение” в файле листинга. Ее синтаксис: %PCNT размер

    Здесь размер — число столбцов, которое необходимо отвести для смещения в текущем ассемблируемом сегменте.
    По умолчанию TASM устанавливает размер, равный 4 для обычных 16-битных сегментов (атрибут размера адреса use16) и 8 для 32-битных сегментов (атрибут размера адреса use32). Директива %PCNT позволяет переопределить эти используемые по умолчанию значения.

    %TITLE
    Директива %TITLE задает заголовок файла листинга. Ее синтаксис: %TITLE “текст”

    Здесь текст — строка, которая будет выводиться в верхней части каждой страницы после имени исходного файла и перед заголовком, заданным по директиве %SUBTTL.
    В отличие от других директив, %TITLE можно использовать в программе только один раз.

    %SUBTTL
    Директива %SUBTTL задает подзаголовок файла листинга. Ее синтаксис: %SUBTTL “текст”

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

    %TABSIZE
    Директива %TABSIZE задает позицию табуляции в файле листинга. Ее синтаксис: %TABSIZE размер

    Здесь размер — число столбцов между двумя позициями табуляции в файле листинга (по умолчанию 8 столбцов).

    %TEXT
    Директива %TEXT используется для задания длины поля исходного текста в файле листинга. Ее синтаксис: %TEXT размер

    Здесь размер — число столбцов, используемых для вывода исходных строк. Если размер строки превышает длину этого поля, то строка будет либо усекаться, либо переноситься на следующую строку, в зависимости от директив %TRUNC или %NOTRUNC. 


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

    Выберите нужный тип ошибки:

    Сообщения об ошибках Сообщения о фатальных ошибках



     
     
     
     
     
     
     

    Сообщения об ошибках

    32-bit segment not allowed without .386
    Argument needs type override
    Argument to operation or instruction has illegal size
    Arithmetic overflow
    ASSUME must be segment register
    Bad keyword in SEGMENT statement
    Can't add relative quantities
    Can't address with currently ASSUMEd segment registers
    Can't convert to pointer
    Can't emulate 8087 instruction
    Can't make variable public
    Can't override ES segment
    Can't subtract dissimilar relative quantities
    Can't use macro name in expression
    Can't use this outside macro
    Code or data emission to undeclared segment
    Constant assumed to mean Immediate const
    Constant too large
    CS not correctly assumed
    CS override in protected mode
    CS unreachable from current segment
    Declaration needs name
    Directive ignored in Turbo Pascal model
    Directive not allowed inside structure definition
    Duplicate dummy arguments:
    Expecting METHOD keyword
    Expecting offset quantity
    Expecting offset or pointer quantity
    Expecting pointer type
    Expecting record field name
    Expecting register ID
    Expecting scalar type
    Expecting segment or group quantity
    Extra characters on line
    Forward reference needs override
    Global type doesn't match symbol type
    ID not member of structure
    Illegal forward reference
    Illegal immediate
    Illegal indexing mode
    Illegal instruction
    Illegal instruction for currently selected processor(s)
    Illegal local argument
    Illegal local symbol prefix
    Illegal mаcro argument
    Illegal memory reference
    Illegal number
    Illegal origin address
    Illegal override in structure
    Illegal override register
    Illegal radix
    Illegal register for instruction
    Illegal register multiplier
    Illegal segment address
    Illegal use of constant
    Illegal use of register
    Illegal use of segment register
    Illegal USES register
    Illegal version ID
    Illegal warning ID
    Instruction can be compacted with override
    Invalid model type
    Invalid operand(s) to instruction
    Labels can't start with numeric characters
    Line too long - truncated
    Location counter overflow
    Method call requires object name
    Missing argument list
    Missing argument or <
    Missing argument size variable
    Missing COMM ID
    Missing dummy argument
    Missing end quote
    Missing macro ID
    Missing module name
    Missing or illegal type specifier
    Missing table member ID
    Missing term in list
    Missing text macro
    Model must be specified first
    Module is pass-dependant - compatibility pass was done
    Name must come first
    Near jump or call to different CS
    Need address or register
    Need colon
    Need expression
    Need file name after INCLUDE
    Need left parenthesis
    Need method name
    Need pointer expression
    Need quoted string
    Need register in expression
    Need right angle bracket
    Need right curly bracket
    Need right parenthesis
    Need right square bracket
    Need stack argument
    Need structure member name
    Not expecting group or segment quantity
    One non-null field allowed per union expansion
    Only one startup sequence allowed
    Open conditional
    Open procedure
    Open segment
    Open structure definition
    Operand types do not match
    Operation illegal for static table member
    Pass-dependant construction encountered
    Pointer expression needs brackets
    Positive count expecting
    Record field too large
    Record member not found
    Recursive definition not allowed for EQU
    Register must be AL or AX
    Register must be DX
    Relative jump out of range by __ bytes
    Relative quantity illegal
    Reserved word used as symbol
    Rotate count must be constant or CL
    Rotate count out of range
    Segment alignment not strict enough
    Segment attributes illegally redefined
    Segment name is superfluous
    String too long
    Symbol already defined:__
    Symbol already different kind
    Symbol has no width or mask
    Symbol is not a segment or already part of a group
    Text macro expansion exceeds maximum line length
    Too few operands to instruction
    Too many errors or warnings
    Too many initial values
    Too many register multipliers in expression
    Too many registers in expression
    Too many USES registers
    Trailling null value assumed
    Undefined symbol
    Unexpected end of file (no END directive)
    Unknown character
    Unmatched ENDP:_
    Unmatched ENDS:_
    User-generated error
    USES has no effect without language
    Value out of range
    32-bit segment not allowed without .386
    32-битовые флаги без директивы .386 не допускаются.
    Argument needs type override
    Требуется явно указать тип операнда. Требуется явно указать размер (тип) выражения, так как транслятор не может сделать этого, исходя только из контекста (см. урок 5). Отметим лишь, что такого рода ошибки исправляются с помощью оператора PTR, позволяющего сообщить транслятору истинный размер операнда.
    Argument to operation or instruction has illegal size
    Операнд операции или команды имеет недопустимый размер.
    Arithmetic overflow
    Арифметическое переполнение. Потеря значащих цифр при вычислении значения выражения.
    ASSUME must be segment register
    В директиве ASSUME должен быть указан сегментный регистр.
    Bad keyword in SEGMENT statement
    Неверное ключевое слово в операторе SEGMENT. Один из параметров директивы SEGMENT: тип выравнивания, тип объединения или тип сегмента, — имеет недопустимое значение.
    Can't add relative quantities
    Нельзя складывать относительные адреса.
    Can't address with currently ASSUMEd segment registers
    Невозможна адресация из текущих, установленных директивой assume, сегментных регистров. В выражении содержится ссылка на переменную, для доступа к которой не специфицирован сегментный регистр.
    Can't convert to pointer
    Невозможно преобразование в указатель.
    Can't emulate 8087 instruction
    Невозможна эмуляция команд сопроцессора 8087.
    Can't make variable public
    Переменная не может быть объявлена как PUBLIC. Скорее всего, это вызвано тем, что данная переменная была уже где-то ранее объявлена таким образом, что уже не может быть определена как общая (PUBLIC).
    Can't override ES segment
    Нельзя переопределить сегмент ES. Это сообщение характерно для операций типа цепочечных. В некоторых из них нельзя переопределять местоположение сегментной части адреса операнда.
    Can't subtract dissimilar relative quantities
    Недопустимое вычитание относительных адресов. Выражение содержит операцию вычитания двух адресов, которая для данных адресов является недопустимой. К примеру, это может случиться, если адреса находятся в разных сегментах.
    Can't use macro name in expression
    Недопустимо использование имени макрокоманды в качестве операнда выражения.
    Can't use this outside macro
    Использование данного оператора недопустимо вне макроопределения.
    Code or data emission to undeclared segment
    Не объявлен сегмент для кода или данных. Это может случиться, если предложение программы, генерирующее код или данные, не принадлежит ни одному из сегментов, объявленных директивами SEGMENT.
    Constant assumed to mean Immediate const
    Константа интерпретируется как непосредственная.
    Constant too large
    Слишком большая константа. Константа превышает допустимую для данного режима величину. Например, числа, большие 0ffffh, можно использовать, если только директивой .386/.386P или .486/.486Р разрешены команды процессора i386 или i486.1
    CS not correctly assumed
    Некорректное значение в регистре cs.
    CS override in protected mode
    Переопределение регистра CS в защищенном режиме. Это предупреждающее сообщение выдается, если в командной строке указан параметр /Р.
    CS unreachable from current segment
    CS недостижим из текущего сегмента. При определении метки кода с помощью двоеточия (:) или с помощью директив LABEL или PROC сегментный регистр не указывает на текущий кодовый сегмент или группу, содержащую текущий кодовый сегмент.
    Declaration needs name
    В директиве объявления не указано имя.
    Directive ignored in Turbo Pascal model
    В режиме TPASCAL директива игнорируется.
    Directive not allowed inside structure definition
    Недопустимая директива внутри определения структуры.
    Duplicate dummy arguments:
    Недопустимо использование одинаковых имен для формальных параметров
    Expecting METHOD keyword
    Требуется ключевое слово METHOD.
    Expecting offset quantity
    Требуется указать величину смещения.
    Expecting offset or pointer quantity
    Требуется указать смещение или указатель.
    Expecting pointer type
    Операнд должен быть указателем. Означает, что операндом текущей команды должен быть адрес памяти.
    Expecting record field name
    Требуется имя поля записи. Инструкция SETFIELD или GETFIELD использована без последующего имени поля.
    Expecting register ID
    Требуется идентификатор регистра
    Expecting scalar type
    Операнд должен быть константой.
    Expecting segment or group quantity
    Должно быть указано имя сегмента или группы.
    Extra characters on line
    Лишние символы в строке.
    Forward reference needs override
    Ошибка при использовании умолчания для ссылки вперед.
    Global type doesn't match symbol type
    Тип, указанный в директиве GLOBAL, не совпадает с действительным типом имени идентификатора.
    ID not member of structure
    Идентификатор не является полем структуры.
    Illegal forward reference
    Недопустимая ссылка вперед.
    Illegal immediate
    Недопустим непосредственный операнд.
    Illegal indexing mode
    Недопустимый режим индексации.
    Illegal instruction
    Недопустимая команда.
    Illegal instruction for currently selected processor(s)
    Недопустимая команда для выбранного в настоящий момент процессора.
    Illegal local argument
    Недопустимый локальный параметр.
    Illegal local symbol prefix
    Недопустимый префикс для локальных имен идентификаторов.
    Illegal mаcro argument
    Недопустимый параметр макрокоманды.
    Illegal memory reference
    Недопустима ссылка на память.
    Illegal number
    Недопустимое число.
    Illegal origin address
    Недопустимый начальный адрес.
    Illegal override in structure
    Недопустимое переопределение в структуре.
    Illegal override register
    Недопустимое переопределение регистра.
    Illegal radix
    Недопустимое основание системы счисления. В директиве .RADIX в качестве основания системы счисления указано недопустимое число. Основанием системы счисления могут быть только числа: 2, 8, 10 и 16. Эти числа интерпретируются как десятичные независимо от текущей системы счисления.
    Illegal register for instruction
    Недопустимый регистр в инструкции. В качестве операнда инструкций SETFIELD и GETFIELD использован недопустимый регистр.
    Illegal register multiplier
    Недопустимо указание множителя для регистра.
    Illegal segment address
    Недопустимый сегментный адрес.
    Illegal use of constant
    Недопустимо использование константы.
    Illegal use of register
    Недопустимо использование регистра.
    Illegal use of segment register
    Недопустимо использование сегментного регистра.
    Illegal USES register
    В директиве USES указан недопустимый регистр.
    Illegal version ID
    Недопустимый идентификатор версии.
    Illegal warning ID
    Недопустимый идентификатор предупреждающего сообщения.
    Instruction can be compacted with override
    Возможно сокращение длины команды, если явно указать тип имени. Из-за наличия ссылки вперед на имя идентификатора объектный код содержит дополнительные команды NOP. Этим самым транслятор резервирует место для размещения адреса идентификатора. При необходимости код можно сократить, убрав ссылку вперед, либо явно указать тип символического имени.
    Invalid model type
    Недопустимая модель памяти.
    Invalid operand(s) to instruction
    Недопустимый операнд(ы) для данной команды.
    Labels can't start with numeric characters
    Метки не могут начинаться с цифровых символов.
    Line too long - truncated
    Строка слишком длинная, и поэтому производится усечение.
    Location counter overflow
    Переполнение счетчика адреса.
    Method call requires object name
    В вызове метода необходимо имя объекта.
    Missing argument list
    Отсутствует список аргументов.
    Missing argument or <
    Отсутствует аргумент либо не указана угловая скобка <.
    Missing argument size variable
    Отсутствует переменная для размера блока параметров.
    Missing COMM ID
    Отсутствует идентификатор в директиве COMM.
    Missing dummy argument
    Отсутствует формальный параметр.
    Missing end quote
    Отсутствует закрывающая кавычка.
    Missing macro ID
    Отсутствует идентификатор макрокоманды.
    Missing module name
    Отсутствует имя модуля.
    Missing or illegal type specifier
    Отсутствует или неверно указан спецификатор типа.
    Missing table member ID
    Пропущен идентификатор элемента таблицы.
    Missing term in list
    Отсутствует член в списке параметров.
    Missing text macro
    Отсутствует текстовая макрокоманда.
    Model must be specified first
    Сначала должна быть указана модель памяти.
    Module is pass-dependant — compatibility pass was done
    Модуль зависит от прохода. Выполнен проход, обеспечивающий совместимость с MASM.
    Name must come first
    Имя должно быть указано первым.
    Near jump or call to different CS
    Адресат ближнего перехода или вызова находится в другом кодовом сегменте.
    Need address or register
    Требуется указать адрес или регистр.
    Need colon
    Требуется двоеточие.
    Need expression
    Требуется указать выражение.
    Need file name after INCLUDE
    В директиве INCLUDE должно быть указано имя файла.
    Need left parenthesis
    Отсутствует левая круглая скобка.
    Need method name
    Требуется имя метода.
    Need pointer expression
    Требуется выражение-указатель.
    Need quoted string
    Требуется указать строку в кавычках.
    Need register in expression
    В выражении требуется указать имя регистра.
    Need right angle bracket
    Отсутствует правая угловая скобка.
    Need right curly bracket
    Требуется правая фигурная скобка.
    Need right parenthesis
    Отсутствует правая круглая скобка.
    Need right square bracket
    Отсутствует правая квадратная скобка.
    Need stack argument
    Не указан стековый параметр в команде арифметики с плавающей запятой.
    Need structure member name
    Не указано имя поля структуры.
    Not expecting group or segment quantity
    Использование имени группы или сегмента недопустимо.
    One non-null field allowed per union expansion
    При расширении объединения допускается указывать только одно поле непустым.
    Only one startup sequence allowed
    Допускается только одна директива генерации кода инициализации.
    Open conditional
    Открытый условный блок. После завершающей программу директивы END обнаружен незакрытый условно ассемблируемый блок, открытый одной из директив IFxxx.
    Open procedure
    Открытая процедура. После завершающей программу директивы END обнаружен незакрытый директивой ENDР блок описания процедуры, открытый где-то в программе директивой PROC.
    Open segment
    Открытый сегмент. После завершающей программу директивы END обнаружен незакрытый директивой ENDS сегмент, открытый где-то в программе директивой SEGMENT.
    Open structure definition
    Не указан конец определения структуры (директива ENDS).
    Operand types do not match
    Не совпадают типы операндов. Тип одного из операндов команды не совпадает с типом другого операнда либо не является допустимым для данной команды.
    Operation illegal for static table member
    Для статического элемента таблицы операция не допускается.
    Pass-dependant construction encountered
    Обнаружена конструкция, зависящая от прохода. Данную ошибку можно исправить, убрав ссылки вперед либо указав нужное число проходов транслятора в опции командной строки /m.
    Pointer expression needs brackets
    Адресное выражение должно быть заключено в квадратные скобки.
    Positive count expecting
    Счетчик должен быть положительным.
    Record field too large
    Слишком длинное поле в записи.
    Record member not found
    Не найден статический элемент записи.
    Recursive definition not allowed for EQU
    Рекурсивное определение недопустимо в директиве EQU.
    Register must be AL or AX
    Допустимо указание только регистра al или ax.
    Register must be DX
    Допустимо указание только регистра dx.
    Relative jump out of range by __ bytes
    Адрес назначения условного перехода превышает допустимый предел на __ байт.
    Relative quantity illegal
    Недопустимый относительный адрес. Ссылка на адрес памяти не может быть разрешена на этапе ассемблирования.
    Reserved word used as symbol
    Зарезервированное слово используется в качестве имени идентификатора.
    Rotate count must be constant or CL
    Счетчик в командах сдвига должен быть указан с помощью константы или регистра cl.
    Rotate count out of range
    Недопустимое значение для счетчика сдвига.
    Segment alignment not strict enough
    Выравнивание сегмента недостаточно точное.
    Segment attributes illegally redefined
    Недопустимое переопределение атрибутов сегмента. Суть здесь в том, что пользователь может повторно открывать уже определенный ранее сегмент. Но при этом атрибуты этого сегмента должны иметь те же самые значения либо вообще быть опущены (тогда будут взяты прежние значения).
    Segment name is superfluous
    Имя сегмента игнорируется.
    String too long
    Слишком длинная строка. Длина указанной в кавычках строки превышает 255 символов.
    Symbol already defined: __
    Имя идентификатора уже определено.
    Symbol already different kind
    Имя идентификатора уже объявлено с другим типом.
    Symbol has no width or mask
    Имя идентификатора не может быть использовано в операциях WIDTH и MASK.
    Symbol is not a segment or already part of a group
    Имя идентификатора не является именем сегмента либо уже определено в группе.
    Text macro expansion exceeds maximum line length
    Расширение текстовой макрокоманды превышает максимально допустимую длину.
    Too few operands to instruction
    В команде не хватает операндов.
    Too many errors or warnings
    Слишком много ошибок или предупреждений. Число сообщений об ошибках превысило максимально возможное число — 100.
    Too many initial values
    Слишком много начальных значений.
    Too many register multipliers in expression
    В выражении содержится слишком много множителей для регистров.
    Too many registers in expression
    В выражении указано слишком много регистров.
    Too many USES registers
    Слишком много регистров в директиве USES.
    Trailling null value assumed
    Предполагается конечное пустое значение.
    Undefined symbol
    Идентификатор не определен.
    Unexpected end of file (no END directive)
    Неожиданный конец файла (нет директивы END).
    Unknown character
    Неизвестный символ.
    Unmatched ENDP:_
    Непарная директива ENDP:_.
    Unmatched ENDS:_
    Непарная директива ENDS:_.
    User-generated error
    Ошибка, сгенерированная пользователем. Сообщение выдается в результате выполнения одной из директив генерирования ошибки.
    USES has no effect without language
    USES игнорируется без спецификации языка.
    Value out of range
    Значение константы превышает допустимое значение.