Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Учебное пособие 3000239.doc
Скачиваний:
23
Добавлен:
30.04.2022
Размер:
1.12 Mб
Скачать

4. Переходы. Циклы

4.1. Безусловный переход

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

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

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

Изучение команд перехода начнем с безусловного перехода.

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

Безусловный переход (jumр): JMP ор

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

Прямой переход

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

JMP <метка>

Пример:

JMP L ;следующей будет выполняться

;команда с меткой L

L:MOV AX,0

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

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

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

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

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

Однако в дальнейшем мы не будем обращать внимание на эту деталь, поскольку для языка ассемблера она в общем-то не существенна.

В ПК имеются две машинные команды прямого перехода, в одной из которых относительный адрес перехода задается в виде байта (такая команда называется коротким переходом), а в другой – в виде слова (это команда длинного перехода). В каждой из этих команд операнд рассматривается как целое со знаком (от -128 до +127 или от -215 до 215-1), поэтому при сложении его с IP значение этого регистра может как увеличиться, так и уменьшиться, т. е. возможен и переход вперед, и переход назад.

Естественно, возникает вопрос: в чем выгода от того, что указывается не сам адрес перехода, а его расстояние от команды перехода? Это объясняется стремлением создателей ПК сэкономить память, занимаемую командами перехода. Если в команде указывать сам адрес перехода, то на него придется всегда отводить слово (2 байта). Но практика показывает, что в большинстве случаев переходы делаются на команды, расположенные недалеко от команд перехода, поэтому разность между адресами этих двух команд, как правило, небольшая, для нее достаточно и байта.

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

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

Оператор SHORT

Зачем же мы тогда обо всем этом рассказали? Дело в следующем. Встречая символьную команду перехода с меткой, ассемблер вычисляет разность между адресом этой метки и адресом самой команды пере­хода и оценивает величину этой разности. Если она небольшая, укладывается в байт, тогда ассемблер формирует машинную команду короткого перехода (она занимает 2 байта), а если разность большая – формирует команду длинного перехода (3 байта). Однако сделать такой выбор ассемблер может, только если метка была описана до команды перехода, т. е. если эта метка является ссылкой назад.

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

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

JMP L ;длинный переход (3 байта)

JMP SHORT L ;короткий переход (2 байта)

...

L: …

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

Оператор SHORT можно ставить и перед меткой, описанной раньше, т. е. при переходе назад, однако в этом случае ассемблер проигнорирует этот оператор, поскольку он и так будет знать «расстояние» перехода.

Косвенный переход

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

JMP r16 или JMP m16

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

Примеры ([х] - содержимое ячейки или регистра):

A DW L

...

JMP A ;goto [A]=goto L

...

MOV DX, A ;DX=L

JMP DX ;goto [DX]=goto L

...

L: ...

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

А сейчас рассмотрим одну проблему, с которой сталкивается ассемблер при трансляции команд безусловного перехода . Возьмем команду JMP Z, где Z – некоторое имя (но не имя регистра). Что это такое – прямой переход по метке Z или косвенный переход по адресу из ячейки Z? Если имя Z описано до этой команды, то здесь проблемы нет: если именем Z помечена команда (рис. 25, а), то это переход по метке, а если имя Z описано в директиве DW (рис. 25, б), то это косвенный переход.

Z: INC AX Z DW L JMP Z ;goto ?

… … …

JMP Z ; goto Z JMP Z ; goto L Z …

а) б) в)

Рис. 25. Варианты организации безусловного перехода

Но если Z – ссылка вперед, т. е. это имя описывается позже (рис. 25, в), тогда ассемблер не будет знать, какой здесь переход. Чтобы снять эту неоднозначность, в языке ассемблера принято следующее соглашение: в подобной ситуации ассемблер всегда считает, что Z – метка, и потому всегда формирует команду прямого перехода по этой метке (причем команду длинного перехода). Если же затем обнаружится, что Z – не метка, то будет зафиксирована ошибка.

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

Итак, при переходах вперед имеем следующие случаи:

Случай 1

JMP Z ; goto Z

Z: …

Случай 2

JMP Z ;ошибка

Z DW L

Случай 3

JMP WORD PTR Z ;goto L

Z DW L