На главную
Подписка
Новости


Рейтинг@Mail.ru











Главная / ASSEMBLER / Учебник по основам языка программирования ASSEMBLER / 3.2. Циклы и условные переходы Сделать домашней страницей Добавить в избранное Написать писмо

НАЗАД СОДЕРЖАНИЕ ВПЕРЕД

3.2. Циклы и условные переходы


Циклы, позволяющие выполнить некоторый участок программы многократно, в любом языке являются одной из наиболее употребительных конструкций. В системе команд МП 86 циклы реализуются, главным образом, с помощью команды loop (петля), хотя имеются и другие способы организации циклов. Во всех случаях число шагов в цикле определяется содержимым регистра СХ, поэтому максимальное число шагов составляет 64 К.
Рассмотрим простой пример организации цикла. Пусть в программе зарезервировано место для массива размером 10000 слов, и этот массив надо заполнить натуральным рядом чисел от 0 до 9999. Эти числа, заполняющие последовательные элементы массива, иногда называют числами-заполнителями. Соответствующий фрагмент программы будет выглядеть следующим образом:

На этапе подготовки мы заносим в регистр ВХ относительный адрес начала массива, отождествляемый с его именем array, устанавливаем начальное значение индекса элемента массива в регистре SI (с таким же успехом можно бьшо взять DI) и начальное значение числа-заполнителя. Сам цикл состоит из трех команд - единственной содержательной команды засылки числа-заполнителя в очередной элемент массива (по адресу, который вычисляется, как сумма содержимого регистров ВХ и SI), а также модификации числа-заполнителя и индекса очередного элемента массива. Завершающей командой loop управление передается на метку fill, и цикл повторяется столько раз, каково содержимое СХ, в данном случае 10000 шагов.
Следует обратить внимание на команду модификации индекса - в каждом шаге к содержимому SI добавляется 2, так как массив состоит из двухбайтовых слов. Если бы нужно бьшо заполнить байтовый массив, то в каждом шаге содержимое регистра цикла SI следовало увеличивать на 1.
Стоит отметить некоторые детали, связанные с механизмом выполнения команды loop. При реализации этой команды процессор сначала уменьшает содержимое регистра СХ на 1, а затем сравнивает полученное число с нулем. Если СХ > 0, переход на указанную метку выполняется. Если СХ = 0, цикл разрывается и процессор переходит на команду, следующую за командой loop. Поэтому после нормального выхода из цикла содержимое СХ всегда равно 0.
Другое обстоятельство связано с кодированием команды loop. В ее коде под смещение к точке перехода отводится всего 1 байт. Поскольку смещение должно являться величиной со знаком, максимальное расстояние, на которое можно передать управление командой loop, составляет от -128 до +127 байт (хотя довольно трудно представить себе цикл, в котором переход осуществляется вперед). Другими словами, тело цикла ограничивается всего 128 байтами. Если циклически повторяемый фрагмент программы имеет большую длину, цикл придется организовать другим, более сложным способом:

В этом, весьма типичном фрагменте мы "вручную" уменьшаем содержимое счетчика цикла и сравниваем полученное значение с 0. Если СХ = О, это значит, что в цикле выполнено заданное число шагов, и командой условного перехода je осуществляется переход на продолжение программы (метка finish). Если СХ еще не равно нулю, командой безусловного перехода jmp осущестатяется возврат в начало цикла. Как было показано в гл. 2, команда jmp позволяет перейти в любую точку сегмента, и огра-ничение на размер тела цикла снимается.
При необходимости организовать вложенные циклы, для сохранения счетчика внешнего цикла на время выполнения внутреннего удобно воспользоваться стеком. В следующем фрагменте организуется временная задержка длительностью несколько секунд (конкретная величина задержки зависит от скорости работы процессора).

Программные задержки удобно использовать при отладке программ, чтобы замедлить их работу и успеть рассмотреть их частичные результаты; иногда программные задержки позволяют синхронизовать работу аппаратуры, подключенной к компьютеру, если скорость отработки аппаратурой посылаемых в нее из компьютера команд меньше скорости процессора.
В приведенном выше фрагменте внешний цикл выполняется 2000 раз; внутренний - 65536 раз. При счете числа шагов внутреннего цикла используется явление оборачивания, которое уже упоминалось ранее. Начальное значение в регистре СХ равно нулю; после выполнения тела цикла 1 раз команда loop уменьшает содержимое СХ на 1, что дает число FFFFh (которое можно рассматривать, как -1). В результате цикл повторяется еще 65535 раз, а в сумме - точно 64 К шагов.
Команда loop внутреннего цикла передает управление на саму себя, т.е. тело внутреннего цикла состоит из единственной команды loop. В этом нет ничего незаконного. Любая команда, в том числе и loop, требует какого-то времени для своего выполнения, и повторение 64 К раз команды loop дает некоторую временную задержку (на современных процессорах порядка тысячной доли секунды).
Перейдем теперь к рассмотрению команд условных переходов.
В приведенном выше фрагменте для реализации длинного цикла использовалась команда условного перехода по равенству je. В системе команд МП 86 имеется свыше трех десятков команд условных переходов, позволяющих осуществлять переходы при наличии разнообразных усло-вий: равенства, неравенства, положительности или отрицательности ре-зультата и проч. При выполнении всех этих команд процессор анализирует содержимое регистра флагов и осуществляет (или не осуществляет) переход на указанную метку в зависимости от состояния отдельных флагов или их комбинаций. Поскольку на состояние регистра флагов влияют многие команды процессора, командами условных переходов можно пользоваться не только после команд сравнения или анализа, но и после многих других команд, если внимательно изучить влияние этих команд на флаги процессора. Приведем несколько абстрактных примеров.

В гл. 2 отмечалось, что двоичные числа, записываемые в регистры процессора или ячейки памяти, можно рассматривать, либо как числа существенно положительные, т.е. числа без знака, либо как числа со знаком. Например, адреса ячеек, разумеется, не могут быть отрицательными. Поэтому число FFFFh, если по смыслу программы оно является адресом, обозначает 65535. Если, однако, то же число FFFFh получилось в арифметической операции вычитания 2 из 1, то его надо рассматривать, как - 1. Точно так же понятие знака бессмысленно по отношению к кодам символов, которые с равным успехом могут принимать любое значение из диапазона 0...255. С другой стороны, мы можем условно считать, что коды символов первой половины таблицы ASCII положительны, а коды второй половины таблицы (у них установлен старший бит) отрицательны, и использовать для обработки символов команды, чувствительные к знаку.
В составе команд условных переходов имеются две группы команд для сравнения чисел без знака (это команды ja, jae, jb, jbc, jna, jnae, jnb и jnbe) и чисел со знаком (jg, jge, jl, jle, jng, jnge, jnl и jnle). В аббревиатурах этих команд для сравнения чисел без знака используются слова above (выше) и below (ниже), а для чисел со знаком - слова greater (больше) и less (меньше).
Разница между теми и другими командами условных переходов заключается в том, что команды для чисел со знаком рассматривают понятия "больше-меньше" применительно к числовой оси -32К...0...+32К, а команды для чисел без знака - применительно к числовой оси 0...64К. Поэтому для первых команд число 7FFFh (+32767) больше числа S000h (-32768), а для вторых число 7FFFh (32767) меньше числа S000h (32768). Аналогично, команды для чисел со знаком считают, что 0 больше, чем FFFFh (-1), а команды для чисел без знака - меньше.
Рассмотрим пример использования команд условных переходов для обработки символов. Пусть мы вводим с клавиатуры некоторую строку символов (например, имя файла), и хотим, чтобы в программе эта строка была записана прописными буквами, независимо от того, какие буквы использовались при ее вводе. Между прочим, при вводе с клавиатуры команд DOS система всегда выполняет эту операцию, поэтому и команды, и ключи, и имена файлов можно вводить как прописными, так и строчными буквами - DOS во всех случаях преобразует все буквы в прописные.

В начале программы на экран выводится служебное сообщение "Вводите!", которое служит запросом программы, адресованным пользователю. Далее с помощью функции DOS 3Fh выполняется ввод строки текста с клавиатуры. Функция 3Fh может вводить данные из разных устройств - файлов, последовательного порта, клавиатуры. Различные устройства идентифицируются их дескрипторами. При работе с файлами дескриптор каждого файла создается системой в процессе операции открытия или создания этого файла, а для стандартных устройств - клавиатуры, экрана, принтера и последовательного порта действуют дескрипторы, закрепляемые за этими устройствами при загрузке системы. Для ввода с клавиатуры используется дескриптор 0, для вывода на экран дескриптор 1.
При вызове функции 3Fh в регистр ВХ следует занести требуемый дескриптор, в регистр DX - адрес области в программе, выделенной для приема вводимых с клавиатуры символов, а в регистр СХ - максимальное число вводимых символов. Мы считаем, что пользователь не будет вводить более 80 символов. Можно ввести и меньше; в любом случае ввод строки следует завершить нажатием клавиши <Enter>. Функция 3Fh, отработав, вернет в регистре АХ реальное число введенных символов (включая коды 13 и 10, образуемые при нажатии клавиши <Enter>). В примере 3.5 число введенных символов сохраняется в ячейке actlen с целью использования далее по ходу программы.
Далее в цикле из actlen шагов выполняется анализ каждого введенного символа путем сравнения с границами диапазонов строчных русских букв. Русские строчные буквы размещаются в двух диапазонах кодов ASCII (а...п и р...с), причем для преобразования в прописные букв первого диапазона их код следует уменьшать на 20h, а для преобразования букв второго диапазона - на 50h. Поэтому анализ проводится с помощью четырех команд сравнения сmр и соответствующих команд условных переходов. Модифицированный символ записывается на то же место в буфере buf.
После завершения анализа и преобразования введенных символов, выполняется контрольный вывод содержимого buf на экран. Поскольку мы заранее не знаем, сколько символов будет введено, вывод на экран осуществляется функцией 40h, среди параметров которой указывается число выводимых символов. Так же, как и в случае функции ввода 3Fh, для функции вывода 40h в регистре ВХ необходимо указать дескриптор устройства ввода, в данном случае экрана, а в регистре DX - адрес выво-димой строки.
Коды символов являются числами без знака, и использование в данном случае команд условных переходов для чисел без знака представляется логичным и даже единственно возможным. Если, однако, внимательно рассмотреть понятия больше-меньше для чисел со знаком и без знака, то легко увидеть, что пока мы сравниваем друг с другом только "положительные" или только "отрицательные" числа, команда ja эквивалентна команде jg, а команда jb эквивалентна команде jl. Однако при сравнении, например, кодов цифр с кодами русских букв, правильный результат можно получить лишь при использовании команд переходов для чисел без знака. Впрочем, всегда нагляднее и надежнее использовать те команды, которые соответствуют существу рассматриваемых данных, даже если такой же правильный результат получится и при использовании "неправильных" команд.
Более отчетливо разница между числами со знаком и без знака проявляется при использовании арифметических операций, например, операций умножения или деления. Здесь для чисел со знаком и чисел без знака предусмотрены отдельные команды:

mul - команда умножения чисел без знака;
imul - команда умножения чисел со знаком;
div - команда деления чисел без знака;
idiv - команда деления чисел со знаком.

Поясним различия этих команд на формальных примерах.

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

Здесь действие команд mul и imul над одними и теми же операндами дает разные результаты. В первом примере число без знака FCh, которое интерпретируется, как 252, умножается на 4, давая в результате число без знака 3F0, т.е. 1008. Во втором примере то же число FCh рассматривается, как число со знаком. В этом случае оно составляет -4. Умножение на 4 дает FFF0h, т.е. -16.

НАЗАД СОДЕРЖАНИЕ ВПЕРЕД

Hosted by uCoz