![]() |
КОНСПЕКТЫ ЛЕКЦИЙ, ШПАРГАЛКИ
Информатика и информационные технологии. Команды передачи управления (конспект лекций) Справочник / Конспекты лекций, шпаргалки Оглавление (развернуть) ЛЕКЦИЯ № 18. Команды 1. Команды пересылки данных Для удобства практического применения и отражения их специфики команды данной группы удобнее рассматривать в соответствии с их функциональным назначением, согласно которому их можно разбить на следующие группы команд: 1) пересылки данных общего назначения; 2) ввода-вывода в порт; 3) работы с адресами и указателями; 4) преобразования данных; 5) работы со стеком. Команды пересылки данных общего назначения К этой группе относятся следующие команды: 1) mov - это основная команда пересылки данных. Она реализует самые разнообразные варианты пересылки. Отметим особенности применения этой команды: а) командой mov нельзя осуществить пересылку из одной области памяти в другую. Если такая необходимость возникает, то нужно использовать в качестве промежуточного буфера любой доступный в данный момент регистр общего назначения; б) нельзя загрузить в сегментный регистр значение непосредственно из памяти. Поэтому для выполнения такой загрузки нужно использовать промежуточный объект. Это может быть регистр общего назначения или стек; в) нельзя переслать содержимое одного сегментного регистра в другой сегментный регистр. Это объясняется тем, что в системе команд нет соответствующего кода операции. Но необходимость в таком действии часто возникает. Выполнить такую пересылку можно, используя в качестве промежуточных все те же регистры общего назначения; г) нельзя использовать сегментный регистр CS в качестве операнда назначения. Причина здесь простая. Дело в том, что в архитектуре микропроцессора пара cs: ip всегда содержит адрес команды, которая должна выполняться следующей. Изменение командой mov содержимого регистра CS фактически означало бы операцию перехода, а не пересылки, что недопустимо. 2) xchg - применяют для двунаправленной пересылки данных. Для этой операции можно, конечно, применить последовательность из нескольких команд mov, но из-за того, что операция обмена используется довольно часто, разработчики системы команд микропроцессора посчитали нужным ввести отдельную команду обмена xchg. Естественно, что операнды должны иметь один тип. Не допускается (как и для всех команд ассемблера) обменивать между собой содержимое двух ячеек памяти. Команды ввода-вывода в порт Посмотрите на рисунок 22. На нем показана сильно упрощенная, концептуальная схема управления оборудованием компьютера. Рис. 22. Концептуальная схема управления оборудованием компьютера Как видно из рисунка 22, самым нижним уровнем является уровень BIOS, на котором работа с оборудованием ведется напрямую через порты. Тем самым реализуется концепция независимости от оборудования. При замене оборудования необходимо будет лишь подправить соответствующие функции BIOS, переориентировав их на новые адреса и логику работы портов. Принципиально управлять устройствами напрямую через порты несложно. Сведения о номерах портов, их разрядности, формате управляющей информации приводятся в техническом описании устройства. Необходимо знать лишь конечную цель своих действий, алгоритм, в соответствии с которым работает конкретное устройство, и порядок программирования его портов, т. е., фактически, нужно знать, что и в какой последовательности нужно послать в порт (при записи в него) или считать из него (при чтении) и как следует трактовать эту информацию. Для этого достаточно всего двух команд, присутствующих в системе команд микропроцессора: 1) in аккумулятор, номер_порта - ввод в аккумулятор из порта с номером номер_порта; 2) out порт, аккумулятор - вывод содержимого аккумулятора в порт с номером номер_порта. Команды работы с адресами и указателями памяти При написании программ на ассемблере производится интенсивная работа с адресами операндов, находящимися в памяти. Для поддержки такого рода операций есть специальная группа команд, в которую входят следующие команды: 1) lea назначение, источник - загрузка эффективного адреса; 2) Ids назначение, источник - загрузка указателя в регистр сегмента данных ds; 3) les назначение, источник - загрузка указателя в регистр дополнительного сегмента данных es; 4) lgs назначение, источник - загрузка указателя в регистр дополнительного сегмента данных gs; 5) lfs назначение, источник - загрузка указателя в регистр дополнительного сегмента данных fs; 6) lss назначение, источник - загрузка указателя в регистр сегмента стека ss. Команда lea похожа на команду mov тем, что она также производит пересылку. Однако команда lea производит пересылку не данных, а эффективного адреса данных (т. е. смещения данных относительно начала сегмента данных) в регистр, указанный операндом назначение. Часто для выполнения некоторых действий в программе недостаточно знать значение одного лишь эффективного адреса данных, а необходимо иметь полный указатель на данные. Полный указатель на данные состоит из сегментной составляющей и смещения. Все остальные команды этой группы позволяют получить в паре регистров такой полный указатель на операнд в памяти. При этом имя сегментного регистра, в который помещается сегментная составляющая адреса, определяется кодом операции. Соответственно смещение помещается в регистр общего назначения, указанный операндом назначение. Но не все так просто с операндом источник. На самом деле, в команде в качестве источника нельзя указывать непосредственно имя операнда в памяти, на который мы бы хотели получить указатель. Предварительно необходимо получить само значение полного указателя в некоторой области памяти и указать в команде получения полный адрес имени этой области. Для выполнения этого действия необходимо вспомнить директивы резервирования и инициализации памяти. При применении этих директив возможен частный случай, когда в поле операндов указывается имя другой директивы определения данных (фактически, имя переменной). В этом случае в памяти формируется адрес этой переменной. Какой адрес будет сформирован (эффективный или полный), зависит от применяемой директивы. Если это dw, то в памяти формируется только 16-битное значение эффективного адреса, если же dd - в память записывается полный адрес. Размещение этого адреса в памяти следующее: в младшем слове находится смещение, в старшем - 16-битная сегментная составляющая адреса. Например, при организации работы с цепочкой символов удобно поместить ее начальный адрес в некоторый регистр и далее в цикле модифицировать это значение для последовательного доступа к элементам цепочки. Необходимость использования команд получения полного указателя данных в памяти, т. е. адреса сегмента и значения смещения внутри сегмента, возникает, в частности, при работе с цепочками. Команды преобразования данных К этой группе можно отнести множество команд микропроцессора, но большинство из них имеет те или иные особенности, которые требуют отнести их к другим функциональным группам. Поэтому из всей совокупности команд микропроцессора непосредственно к командам преобразования данных можно отнести только одну команду: xlat [адрес_таблицы_перекодировки] Это очень интересная и полезная команда. Ее действие заключается в том, что она замещает значение в регистре al другим байтом из таблицы в памяти, расположенной по адресу, указанному операндом адрес_таблицы_перекодировки. Слово "таблица" весьма условно, по сути, это просто строка байт. Адрес байта в строке, которым будет производиться замещение содержимого регистра al, определяется суммой (bx) + (al), т. е. содержимое al исполняет роль индекса в байтовом массиве. При работе с командой xlat обратите внимание на следующий тонкий момент. Несмотря на то что в команде указывается адрес строки байт, из которой должно быть извлечено новое значение, этот адрес должен быть предварительно загружен (например, с помощью команды lea) в регистр bх. Таким образом, операнд адрес_таблицы_перекодировки на самом деле не нужен (необязательность операнда показана заключением его в квадратные скобки). Что касается строки байт (таблицы перекодировки), то она представляет собой область памяти размером от 1 до 255 байт (диапазон числа без знака в 8-битном регистре). Команды работы со стеком Эта группа представляет собой набор специализированных команд, ориентированных на организацию гибкой и эффективной работы со стеком. Стек - это область памяти, специально выделяемая для временного хранения данных программы. Важность стека определяется тем, что для него в структуре программы предусмотрен отдельный сегмент. На тот случай, если программист забыл описать сегмент стека в своей программе, компоновщик tlink выдаст предупреждающее сообщение. Для работы со стеком предназначены три регистра: 1) ss - сегментный регистр стека; 2) sp/esp - регистр указателя стека; 3) bp/ebp - регистр указателя базы кадра стека. Размер стека зависит от режима работы микропроцессора и ограничивается 64 Кбайтами (или 4 Гбайтами в защищенном режиме). В каждый момент времени доступен только один стек, адрес сегмента которого содержится в регистре SS. Этот стек называется текущим. Для того чтобы обратиться к другому стеку ("переключить стек"), необходимо загрузить в регистр ss другой адрес. Регистр SS автоматически используется процессором для выполнения всех команд, работающих со стеком. Перечислим еще некоторые особенности работы со стеком: 1) запись и чтение данных в стеке осуществляется в соответствии с принципом LIFO, 2) по мере записи данных в стек последний растет в сторону младших адресов. Эта особенность заложена в алгоритм команд работы со стеком; 3) при использовании регистров esp/sp и ebp/bp для адресации памяти ассемблер автоматически считает, что содержащиеся в нем значения представляют собой смещения относительно сегментного регистра ss. В общем случае стек организован так, как показано на рисунке 23. Рис. 23. Концептуальная схема организации стека Для работы со стеком предназначены регистры SS, ESP/SP и ЕВР/ВР. Эти регистры используются комплексно, и каждый из них имеет свое функциональное назначение. Регистр ESP/SP всегда указывает на вершину стека, т. е. содержит смещение, по которому в стек был занесен последний элемент. Команды работы со стеком неявно изменяют этот регистр так, чтобы он указывал всегда на последний записанный в стек элемент. Если стек пуст, то значение esp равно адресу последнего байта сегмента, выделенного под стек. При занесении элемента в стек процессор уменьшает значение регистра esp, а затем записывает элемент по адресу новой вершины. При извлечении данных из стека процессор копирует элемент, расположенный по адресу вершины, а затем увеличивает значение регистра указателя стека esp. Таким образом, получается, что стек растет вниз, в сторону уменьшения адресов. Что делать, если нам необходимо получить доступ к элементам не на вершине, а внутри стека? Для этого применяют регистр ЕВР Регистр ЕВР - регистр указателя базы кадра стека. Например, типичным приемом при входе в подпрограмму является передача нужных параметров путем записи их в стек. Если подпрограмма тоже активно работает со стеком, то доступ к этим параметрам становится проблематичным. Выход в том, чтобы после записи нужных данных в стек сохранить адрес вершины стека в указателе кадра (базы) стека - регистре ЕВР. Значение в ЕВР в дальнейшем можно использовать для доступа к переданным параметрам. Начало стека расположено в старших адресах памяти. На рисунке 23 этот адрес обозначен парой ss: fffF. Смещение шТ приведено здесь условно. Реально это значение определяется величиной, которую программист задает при описании сегмента стека в своей программе. Для организации работы со стеком существуют специальные команды записи и чтения. 1. push источник - запись значения источник в вершину стека. Интерес представляет алгоритм работы этой команды, который включает следующие действия (рис. 24): 1) (sp) = (sp) - 2; значение sp уменьшается на 2; 2) значение из источника записывается по адресу, указываемому парой ss: sp. Рис. 24. Принцип работы команды push 2. pop назначение - запись значения из вершины стека по месту, указанному операндом назначение. Значение при этом "снимается" с вершины стека. Алгоритм работы команды pop обратен алгоритму команды push (рис. 25): 1) запись содержимого вершины стека по месту, указанному операндом назначение; 2) (sp) = (sp) + 2; увеличение значения sp. Рис. 25. Принцип работы команды pop 3. pusha - команда групповой записи в стек. По этой команде в стек последовательно записываются регистры ах, сх, dx, bx, sp, bp, si, di. Заметим, что записывается оригинальное содержимое sp, т. е. то, которое было до выдачи команды pusha (рис. 26). Рис. 26. Принцип работы команды pusha 4. pushaw - почти синоним команды pusha В чем разница? Атрибут разрядности может принимать значение use16 или use32. Рассмотрим работу команд pusha и pushaw при каждом из этих атрибутов: 1) use16 - алгоритм работы pushaw аналогичен алгоритму pusha; 2) use32 - pushaw не изменяется (т. е. она нечувствительна к разрядности сегмента и всегда работает с регистрами размером в слово - ах, сх, dx, bx, sp, bp, si, di). Команда pusha чувствительна к установленной разрядности сегмента и при указании 32-разрядного сегмента работает с соответствующими 32-разрядными регистрами, т. е. еах, есх, edx, ebx, esp, ebp, esi, edi. 5. pushad - выполняется аналогично команде pusha, но есть некоторые особенности. Следующие три команды выполняют действия, обратные вышеописанным командам: 1) рора; 2) popaw; 3) popad. Группа команд, описанная ниже, позволяет сохранить в стеке регистр флагов и записать слово или двойное слово в стеке. Отметим, что перечисленные ниже команды - единственные в системе команд микропроцессора, которые позволяют получить доступ (и которые нуждаются в этом доступе) ко всему содержимому регистра флагов. 1. pushf - сохраняет регистр флагов в стеке. Работа этой команды зависит от атрибута размера сегмента: 1) use 16 - в стек записывается регистр flags размером 2 байта; 2) use32 - в стек записывается регистр eflags размером 4 байта. 2. pushfw - сохранение в стеке регистра флагов размером в слово. Всегда работает как pushf с атрибутом use16. 3. pushfd - сохранение в стеке регистра флагов flags или eflags в зависимости от атрибута разрядности сегмента (т. е. то же, что и pushf). Аналогично, следующие три команды выполняют действия, обратные рассмотренным выше операциям: 1) popf; 2) popftv; 3) popfd. И в заключение отметим основные виды операции, когда использование стека практически неизбежно: 1) вызов подпрограмм; 2) временное сохранение значений регистров; 3) определение локальных переменных. 2. Арифметические команды Микропроцессор может выполнять целочисленные операции и операции с плавающей точкой. Для этого в его архитектуре есть два отдельных блока: 1) устройство для выполнения целочисленных операций; 2) устройство для выполнения операций с плавающей точкой. Каждое из этих устройств имеет свою систему команд. В принципе, целочисленное устройство может взять на себя многие функции устройства с плавающей точкой, но это потребует больших вычислительных затрат. Для большинства задач, использующих язык ассемблера, достаточно целочисленной арифметики. Обзор группы арифметических команд и данных Целочисленное вычислительное устройство поддерживает чуть больше десятка арифметических команд. На рисунке 27 приведена классификация команд этой группы. Рис. 27. Классификация арифметических команд Группа арифметических целочисленных команд работает с двумя типами чисел: 1) целыми двоичными числами. Числа могут иметь знаковый разряд или не иметь такового, т. е. быть числами со знаком или без знака; 2) целыми десятичными числами. Рассмотрим машинные форматы, в которых хранятся эти типы данных. Целые двоичные числа Целое двоичное число с фиксированной точкой - это число, закодированное в двоичной системе счисления. Размерность целого двоичного числа может составлять 8, 16 или 32 бит. Знак двоичного числа определяется тем, как интерпретируется старший бит в представлении числа. Это 7,15 или 31-й биты для чисел соответствующей размерности. При этом интересно то, что среди арифметических команд есть всего две команды, которые действительно учитывают этот старший разряд как знаковый, - это команды целочисленного умножения и деления imul и idiv. В остальных случаях ответственность за действия со знаковыми числами и, соответственно, со знаковым разрядом ложится на программиста. Диапазон значений двоичного числа зависит от его размера и трактовки старшего бита либо как старшего значащего бита числа, либо как бита знака числа (табл. 9). Таблица 9. Диапазон значений двоичных чиселДесятичные числа Десятичные числа - специальный вид представления числовой информации, в основу которого положен принцип кодирования каждой десятичной цифры числа группой из четырех бит. При этом каждый байт числа содержит одну или две десятичные цифры в так называемом двоично-десятичном коде (BCD - Binary-Coded Decimal). Микропроцессор хранит BCD-числа в двух форматах (рис. 28): 1) упакованном формате. В этом формате каждый байт содержит две десятичные цифры. Десятичная цифра представляет собой двоичное значение в диапазоне от 0 до 9 размером 4 бита. При этом код старшей цифры числа занимает старшие 4 бита. Следовательно, диапазон представления десятичного упакованного числа в 1 байте составляет от 00 до 99; 2) неупакованном формате. В этом формате каждый байт содержит одну десятичную цифру в четырех младших битах. Старшие 4 бита имеют нулевое значение. Это так называемая зона. Следовательно, диапазон представления десятичного неупакованного числа в 1 байте составляет от 0 до 9. Рис. 28. Представление BCD-чисел Как описать двоично-десятичные числа в программе? Для этого можно использовать только две директивы описания и инициализации данных - db и dt. Возможность применения только этих директив для описания BCD-чисел обусловлена тем, что к таким числам также применим принцип "младший байт по младшему адресу", что очень удобно для их обработки. И вообще, при использовании такого типа данных как BCD-числа, порядок описания этих чисел в программе и алгоритм их обработки - это дело вкуса и личных пристрастий программиста. Это станет ясно после того, как мы ниже рассмотрим основы работы с BCD-числами. Арифметические операции над целыми двоичными числами Сложение двоичных чисел без знака Микропроцессор выполняет сложение операндов по правилам сложения двоичных чисел. Проблем не возникает до тех пор, пока значение результата не превышает размерности поля операнда. Например, при сложении операндов размером в байт результат не должен превышать число 255. Если это происходит, то результат оказывается неверным. Рассмотрим, почему так происходит. К примеру, выполним сложение: 254 + 5 = 259 в двоичном виде. 11111110 + 0000101 = 1 00000011. Результат вышел за пределы 8 бит и правильное его значение укладывается в 9 бит, а в 8-битовом поле операнда осталось значение 3, что, конечно, неверно. В микропроцессоре этот исход сложения прогнозируется и предусмотрены специальные средства для фиксирования подобных ситуаций и их обработки. Так, для фиксирования ситуации выхода за разрядную сетку результата, как в данном случае, предназначен флаг переноса cf. Он располагается в бите 0 регистра флагов EFLAGS/FLAGS. Именно установкой этого флага фиксируется факт переноса единицы из старшего разряда операнда. Естественно, что программист должен учитывать возможность такого исхода операции сложения и предусматривать средства для корректировки. Это предполагает включение участков кода после операции сложения, в которых анализируется флаг cf. Анализ этого флага можно провести различными способами. Самый простой и доступный - использовать команду условного перехода jcc. Эта команда в качестве операнда имеет имя метки в текущем сегменте кода. Переход на эту метку осуществляется в случае, если в результате работы предыдущей команды флаг cf установился в 1. В системе команд микропроцессора имеются три команды двоичного сложения: 1) inc операнд - операция инкремента, т. е. увеличения значения операнда на 1; 2) add операнд_1, операнд_2 - команда сложения с принципом действия: операнд_1 = операнд_1 + операнд_2; 3) adc операнд_1, операнд_2 - команда сложения с учетом флага переноса cf. Принцип действия команды: операнд_1 = операнд_1 + операнд_2 + значение_сГ. Обратите внимание на последнюю команду - это команда сложения, учитывающая перенос единицы из старшего разряда. Механизм появления такой единицы мы уже рассмотрели. Таким образом, команда adc является средством микропроцессора для сложения длинных двоичных чисел, размерность которых превосходит поддерживаемые микропроцессором длины стандартных полей. Сложение двоичных чисел со знаком На самом деле микропроцессор "не подозревает" о различии между числами со знаком и без знака. Вместо этого у него есть средства фиксирования возникновения характерных ситуаций, складывающихся в процессе вычислений. Некоторые из них мы рассмотрели при обсуждении сложения чисел без знака: 1) флаг переноса cf, установка которого в 1 говорит о том, что произошел выход за пределы разрядности операндов; 2) команду adc, которая учитывает возможность такого выхода (перенос из младшего разряда). Другое средство - это регистрация состояния старшего (знакового) разряда операнда, которое осуществляется с помощью флага переполнения of в регистре EFLAGS (бит 11). Вы, конечно, помните, как представляются числа в компьютере: положительные - в двоичном коде, отрицательные - в дополнительном коде. Рассмотрим различные варианты сложения чисел. Примеры призваны показать поведение двух старших битов операндов и правильность результата операции сложения. Пример 30566 = 0111011101100110 + 00687 = 00000010 10101111 = 31253 = 01111010 00010101 Следим за переносами из 14 и 15-го разрядов и правильностью результата: переносов нет, результат правильный. Пример 30566 = 0111011101100110 + 30566 = 0111011101100110 = 1132 = 11101110 11001100 Произошел перенос из 14-го разряда; из 15-го разряда переноса нет. Результат неправильный, так как имеется переполнение - значение числа получилось больше, чем то, которое может иметь 16-битное число со знаком (+32 767). Пример -30566 = 10001000 10011010 + -04875 = 11101100 11110101 = -35441 = 01110101 10001111 Произошел перенос из 15-го разряда, из 14-го разряда нет переноса. Результат неправильный, так как вместо отрицательного числа получилось положительное (в старшем бите находится 0). Пример -4875 = 11101100 11110101 + -4875 = 11101100 11110101 = 09750 = 11011001 11101010 Есть переносы из 14 и 15-го разрядов. Результат правильный. Таким образом, мы исследовали все случаи и выяснили, что ситуация переполнения (установка флага OF в 1) происходит при переносе: 1) из 14-го разряда (для положительных чисел со знаком); 2) из 15-го разряда (для отрицательных чисел). И наоборот, переполнения не происходит (т. е. флаг OF сбрасывается в 0), если есть перенос из обоих разрядов или перенос отсутствует в обоих разрядах. Итак, переполнение регистрируется с помощью флага переполнения of. Дополнительно к флагу of при переносе из старшего разряда устанавливается в 1 и флаг переноса CF Так как микропроцессор не знает о существовании чисел со знаком и без знака, то вся ответственность за правильность действий с получившимися числами ложится на программиста. Проанализировать флаги CF и OF можно командами условного перехода JC\JNC и JO\JNO соответственно. Что же касается команд сложения чисел со знаком, то они те же, что и для чисел без знака. Вычитание двоичных чисел без знака Как и при анализе операции сложения, порассуждаем над сутью процессов, происходящих при выполнении операции вычитания. Если уменьшаемое больше вычитаемого, то проблем нет, - разность положительна, результат верен. Если уменьшаемое меньше вычитаемого, возникает проблема: результат меньше 0, а это уже число со знаком. В этом случае результат необходимо завернуть. Что это означает? При обычном вычитании (в столбик) делают заем 1 из старшего разряда. Микропроцессор поступает аналогично, т. е. занимает 1 из разряда, следующего за старшим, в разрядной сетке операнда. Поясним на примере. Пример 05 = 00000000 00000101 -10 = 00000000 00001010 Для того чтобы произвести вычитание, произведем воображаемый заем из старшего разряда: 100000000 00000101 - 00000000 00001010 = 11111111 11111011 Тем самым, по сути, выполняется действие (65 536 + 5) - 10 = 65 531 0 здесь как бы эквивалентен числу 65536. Результат, конечно, неверен, но микропроцессор считает, что все нормально, хотя факт заема единицы он фиксирует установкой флага переноса cf. Но посмотрите еще раз внимательно на результат операции вычитания. Это же -5 в дополнительном коде! Проведем эксперимент: представим разность в виде суммы 5 + (-10). Пример 5 = 00000000 00000101 + (-10)= 11111111 11110110 = 11111111 11111011 т. е. мы получили тот же результат, что и в предыдущем примере. Таким образом, после команды вычитания чисел без знака нужно анализировать состояние флага СЕ Если он установлен в 1, то это говорит о том, что произошел заем из старшего разряда и результат получился в дополнительном коде. Аналогично командам сложения группа команд вычитания состоит из минимально возможного набора. Эти команды выполняют вычитание по алгоритмам, которые мы сейчас рассматриваем, а учет особых ситуаций должен производиться самим программистом. К командам вычитания относятся следующие: 1) dec операнд - операция декремента, т. е. уменьшения значения операнда на 1; 2) sub операнд_1, операнд_2 - команда вычитания; ее принцип действия: операнд_1 = операнд_1 - операнд_2; 3) sbb операнд_1, операнд_2 - команда вычитания с учетом заема (флага ci): операнд_1 = операнд_1 - операнд_2 - значение_сГ. Как видите, среди команд вычитания есть команда sbb, учитывающая флаг переноса cf. Эта команда подобна adc, но теперь уже флаг cf исполняет роль индикатора заема 1 из старшего разряда при вычитании чисел. Вычитание двоичных чисел со знаком Здесь все несколько сложнее. Микропроцессору незачем иметь два устройства - сложения и вычитания. Достаточно наличия только одного - устройства сложения. Но для вычитания способом сложения чисел со знаком в дополнительном коде необходимо представлять оба операнда - и уменьшаемое, и вычитаемое. Результат тоже нужно рассматривать как значение в дополнительном коде. Но здесь возникают сложности. Прежде всего они связаны с тем, что старший бит операнда рассматривается как знаковый. Рассмотрим пример вычитания 45 - (-127). Пример Вычитание чисел со знаком 1 45 = 0010 1101 - -127 = 1000 0001 = -44 = 1010 1100 Судя по знаковому разряду, результат получился отрицательный, что, в свою очередь, говорит о том, что число нужно рассматривать как дополнение, равное -44. Правильный результат должен быть равен 172. Здесь мы, как и в случае знакового сложения, встретились с переполнением мантиссы, когда значащий разряд числа изменил знаковый разряд операнда. Отследить такую ситуацию можно по содержимому флага переполнения of. Его установка в 1 говорит о том, что результат вышел за диапазон представления знаковых чисел (т. е. изменился старший бит) для операнда данного размера, и программист должен предусмотреть действия по корректировке результата. Пример Вычитание чисел со знаком 2 -45-45 = -45 + (-45)= -90. -45 = 11010011 + -45 = 11010011 = -90 = 1010 0110 Здесь все нормально, флаг переполнения of сброшен в 0, а 1 в знаковом разряде говорит о том, что значение результата - число в дополнительном коде. Вычитание и сложение операндов большой размерности Если вы заметили, команды сложения и вычитания работают с операндами фиксированной размерности: 8, 16, 32 бит. А что делать, если нужно сложить числа большей размерности, например 48 бит, используя 16-разрядные операнды? К примеру, сложим два 48-разрядных числа: Рис. 29. Сложение операндов большой размерности На рисунке 29 по шагам показана технология сложения длинных чисел. Видно, что процесс сложения многобайтных чисел происходит так же, как и при сложении двух чисел "в столбик", - с осуществлением при необходимости переноса 1 в старший разряд. Если нам удастся запрограммировать этот процесс, то мы значительно расширим диапазон двоичных чисел, над которыми мы сможем выполнять операции сложения и вычитания. Принцип вычитания чисел с диапазоном представления, превышающим стандартные разрядные сетки операндов, тот же, что и при сложении, т. е. используется флаг переноса cf. Нужно только представлять себе процесс вычитания в столбик и правильно комбинировать команды микропроцессора с командой sbb. В завершение обсуждения команд сложения и вычитания отметим, что кроме флагов cf и of в регистре eflags есть еще несколько флагов, которые можно использовать с двоичными арифметическими командами. Речь идет о следующих флагах: 1) zf - флаг нуля, который устанавливается в 1, если результат операции равен 0, и в 1, если результат не равен 0; 2) sf - флаг знака, значение которого после арифметических операций (и не только) совпадает со значением старшего бита результата, т. е. с битом 7, 15 или 31. Таким образом, этот флаг можно использовать для операций над числами со знаком. Умножение чисел без знака Для умножения чисел без знака предназначена команда mul сомножитель_1 Как видите, в команде указан всего лишь один операнд-сомножитель. Второй операнд-сомножитель_2 задан неявно. Его местоположение фиксировано и зависит от размера сомножителей. Так как в общем случае результат умножения больше, чем любой из его сомножителей, то его размер и местоположение должны быть тоже определены однозначно. Варианты размеров сомножителей и размещения второго операнда и результата приведены в таблице 10. Таблица 10. Расположение операндов и результата при умножении Из таблицы видно, что произведение состоит из двух частей и в зависимости от размера операндов размещается в двух местах - на месте сомножитель_2 (младшая часть) и в дополнительном регистре ah, dx, edx (старшая часть). Как же динамически (т. е. во время выполнения программы) узнать, что результат достаточно мал и уместился в одном регистре или что он превысил размерность регистра и старшая часть оказалась в другом регистре? Для этого привлекаются уже известные нам по предыдущему обсуждению флаги переноса cf и переполнения of: 1) если старшая часть результата нулевая, то после операции произведения флаги cf = 0 и of = 0; 2) если же эти флаги ненулевые, то это означает, что результат вышел за пределы младшей части произведения и состоит из двух частей, что и нужно учитывать при дальнейшей работе. Умножение чисел со знаком Для умножения чисел со знаком предназначена команда [imul операнд_1, операнд_2, операнд_3] Эта команда выполняется так же, как и команда mul. Отличительной особенностью команды imul является только формирование знака. Если результат мал и умещается в одном регистре (т. е. если cf = of = 0), то содержимое другого регистра (старшей части) является расширением знака - все его биты равны старшему биту (знаковому разряду) младшей части результата. В противном случае (если cf = of = 1) знаком результата является знаковый бит старшей части результата, а знаковый бит младшей части является значащим битом двоичного кода результата. Деление чисел без знака Для деления чисел без знака предназначена команда div делитель Делитель может находиться в памяти или в регистре и иметь размер 8, 16 или 32 бит. Местонахождение делимого фиксировано и так же, как в команде умножения, зависит от размера операндов. Результатом команды деления являются значения частного и остатка. Варианты местоположения и размеров операндов операции деления показаны в таблице 11. Таблица 11. Расположение операндов и результата при делении После выполнения команды деления содержимое флагов неопределенно, но возможно возникновение прерывания с номером 0, называемого "деление на нуль". Этот вид прерывания относится к так называемым исключениям. Эта разновидность прерываний возникает внутри микропроцессора из-за некоторых аномалий во время вычислительного процесса. Прерывание О, "деление на нуль", при выполнении команды div может возникнуть по одной из следующих причин: 1) делитель равен нулю; 2) частное не входит в отведенную под него разрядную сетку, что может произойти в следующих случаях: а) при делении делимого величиной в слово на делитель величиной в байт, причем значение делимого в более чем 256 раз больше значения делителя; б) при делении делимого величиной в двойное слово на делитель величиной в слово, причем значение делимого в более чем 65 536 раз больше значения делителя; в) при делении делимого величиной в учетверенное слово на делитель величиной в двойное слово, причем значение делимого в более чем 4 294 967 296 раз больше значения делителя. Деление чисел со знаком Для деления чисел со знаком предназначена команда idiv делитель Для этой команды справедливы все рассмотренные положения, касающиеся команд и чисел со знаком. Отметим лишь особенности возникновения исключения 0, "деление на нуль", в случае чисел со знаком. Оно возникает при выполнении команды idiv по одной из следующих причин: 1) делитель равен нулю; 2) частное не входит в отведенную для него разрядную сетку. Последнее в свою очередь может произойти: 1) при делении делимого величиной в слово со знаком на делитель величиной в байт со знаком, причем значение делимого в более чем 128 раз больше значения делителя (таким образом, частное не должно находиться вне диапазона от -128 до + 127); 2) при делении делимого величиной в двойное слово со знаком на делитель величиной в слово со знаком, причем значение делимого в более чем 32 768 раз больше значения делителя (таким образом, частное не должно находиться вне диапазона от -32 768 до +32 768); 3) при делении делимого величиной в учетверенное слово со знаком на делитель величиной в двойное слово со знаком, причем значение делимого в более чем 2 147 483 648 раз больше значения делителя (таким образом, частное не должно находиться вне диапазона от -2 147 483 648 до +2 147 483 647). Вспомогательные команды для целочисленных операций В системе команд микропроцессора есть несколько команд, которые могут облегчить программирование алгоритмов, производящих арифметические вычисления. В них могут возникать различные проблемы, для разрешения которых разработчики микропроцессора предусмотрели несколько команд. Команды преобразования типов Что делать, если размеры операндов, участвующих в арифметических операциях, разные? Например, предположим, что в операции сложения один операнд является словом, а другой занимает двойное слово. Выше сказано, что в операции сложения должны участвовать операнды одного формата. Если числа без знака, то выход найти просто. В этом случае можно на базе исходного операнда сформировать новый (формата двойного слова), старшие разряды которого просто заполнить нулями. Сложнее ситуация для чисел со знаком: как динамически, в ходе выполнения программы, учесть знак операнда? Для решения подобных проблем в системе команд микропроцессора есть так называемые команды преобразования типа. Эти команды расширяют байты в слова, слова - в двойные слова и двойные слова - в учетверенные слова (64-разрядные значения). Команды преобразования типа особенно полезны при преобразовании целых со знаком, так как они автоматически заполняют старшие биты вновь формируемого операнда значениями знакового бита старого объекта. Эта операция приводит к целым значениям того же знака и той же величины, что и исходная, но уже в более длинном формате. Подобное преобразование называется операцией распространения знака. Существуют два вида команд преобразования типа. 1. Команды без операндов. Эти команды работают с фиксированными регистрами: 1) cbw (Convert Byte to Word) - команда преобразования байта (в регистре al) в слово (в регистре ах) путем распространения значения старшего бита al на все биты регистра ah; 2) cwd (Convert Word to Double) - команда преобразования слова (в регистре ах) в двойное слово (в регистрах dx: ax) путем распространения значения старшего бита ах на все биты регистра dx; 3) cwde (Convert Word to Double) - команда преобразования слова (в регистре ах) в двойное слово (в регистре еах) путем распространения значения старшего бита ах на все биты старшей половины регистра еах; 4) cdq (Convert Double Word to Quarter Word) - команда преобразования двойного слова (в регистре еах) в учетверенное слово (в регистрах edx: eax) путем распространения значения старшего бита еах на все биты регистра edx. 2. Команды movsx и movzx, относящиеся к командам обработки строк. Эти команды обладают полезным свойством в контексте нашей проблемы: 1) movsx операнд_1, операнд_2 - переслать с распространением знака. Расширяет 8 или 16-разрядное значение операнд_2, которое может быть регистром или операндом в памяти, до 16 или 32-разрядного значения в одном из регистров, используя значение знакового бита для заполнения старших позиций операнд_1. Данную команду удобно использовать для подготовки операндов со знаками к выполнению арифметических действий; 2) movzx операнд_1, операнд_2 - переслать с расширением нулем. Расширяет 8- или 16-разрядное значение операнд_2 до 16- или 32-разрядного с очисткой (заполнением) нулями старших позиций операнд_2. Данную команду удобно использовать для подготовки операндов без знака к выполнению арифметических действий. Другие полезные команды 1. xadd назначение, источник - обмен местами и сложение. Команда позволяет выполнить последовательно два действия: 1) обменять значения назначение и источник; 2) поместить на место операнда назначение сумму: назначение = назначение + источник. 2. neg операнд - отрицание с дополнением до двух. Команда выполняет инвертирование значения операнд. Физически команда выполняет одно действие: операнд = 0 - операнд, т. е. вычитает операнд из нуля. Команду neg операнд можно применять: 1) для смены знака; 2) для выполнения вычитания из константы. Арифметические операции над двоично-десятичными числами В данном разделе мы рассмотрим особенности каждого из четырех основных арифметических действий для упакованных и неупакованных двоично-десятичных чисел. Справедливо может возникнуть вопрос: а зачем нужны BCD-числа? Ответ может быть следующим: BCD-числа нужны в деловых приложениях, т. е. там, где числа должны быть большими и точными. Как мы уже убедились на примере двоичных чисел, операции с такими числами довольно проблематичны для языка ассемблера. К недостаткам использования двоичных чисел можно отнести следующие: 1) значения величин в формате слова и двойного слова имеют ограниченный диапазон. Если программа предназначена для работы в области финансов, то ограничение суммы в рублях величиной 65 536 (для слова) или даже 4 294 967 296 (для двойного слова) будет существенно сужать сферу ее применения; 2) наличие ошибок округления. Представляете себе программу, работающую где-нибудь в банке, которая не учитывает величину остатка при действиях с целыми двоичными числами и оперирует при этом миллиардами? Не хотелось бы быть автором такой программы. Применение чисел с плавающей точкой не спасет - там существует та же проблема округления; 3) представление большого объема результатов в символьном виде (ASCII-коде). Деловые программы не просто выполняют вычисления; одной из целей их использования является оперативная выдача информации пользователю. Для этого, естественно, информация должна быть представлена в символьном виде. Перевод чисел из двоичного кода в ASCII-код требует определенных вычислительных затрат. Число с плавающей точкой еще труднее перевести в символьный вид. А вот если посмотреть на шестнадцатеричное представление неупакованной десятичной цифры и на соответствующий ей символ в таблице ASCII, то видно, что они отличаются на величину 30h. Таким образом, преобразование в символьный вид и обратно получается намного проще и быстрее. Наверное вы уже убедились в важности овладения хотя бы основами действий с десятичными числами. Далее рассмотрим особенности выполнения основных арифметических операций с десятичными числами. Отметим сразу тот факт, что отдельных команд сложения, вычитания, умножения и деления BCD-чисел нет. Сделано это по вполне понятным причинам: размерность таких чисел может быть сколь угодно большой. Складывать и вычитать можно двоично-десятичные числа, как в упакованном формате, так и в неупакованном, а вот делить и умножать можно только неупакованные BCD-числа. Почему это так, будет видно из дальнейшего обсуждения. Арифметические действия над неупакованными BCD-числами Сложение неупакованных BCD-чисел Рассмотрим два случая сложения. Пример Результат сложения не больше 9 6 = 0000 0110 + 3 = 0000 0011 = 9 = 0000 1001 Переноса из младшей тетрады в старшую нет. Результат правильный. Пример Результат сложения больше 9: 06 = 0000 0110 + 07 = 0000 0111 = 13 = 0000 1101 Мы получили уже не BCD-число. Результат неправильный. Правильный результат в неупакованном BCD-формате должен быть таким: 0000 0001 0000 0011 в двоичном представлении (или 13 в десятичном). Проанализировав данную проблему при сложении BCD-чисел (и подобные проблемы при выполнении других арифметических действий) и возможные пути ее решения, разработчики системы команд микропроцессора решили не вводить специальные команды для работы с BCD-числами, а ввести несколько корректировочных команд. Назначение этих команд - в корректировке результата работы обычных арифметических команд для случаев, когда операнды в них являются BCD-числами. В случае вычитания в примере 10 видно, что полученный результат нужно корректировать. Для коррекции операции сложения двух однозначных неупакованных BCD-чисел в системе команд микропроцессора существует специальная команда - ааа (ASCII Adjust for Addition) - коррекция результата сложения для представления в символьном виде. Эта команда не имеет операндов. Она работает неявно только с регистром al и анализирует значение его младшей тетрады: 1) если это значение меньше 9, то флаг cf сбрасывается в О и осуществляется переход к следующей команде; 2) если это значение больше 9, то выполняются следующие действия: а) к содержимому младшей тетрады al (но не к содержимому всего регистра!) прибавляется 6, тем самым значение десятичного результата корректируется в правильную сторону; б) флаг cf устанавливается в 1, тем самым фиксируется перенос в старший разряд, для того чтобы его можно было учесть в последующих действиях. Так, в примере 10, предполагая, что значение суммы 0000 1101 находится в al, после команды ааа в регистре будет 1101 + 0110 = 0011, т. е. двоичное 0000 0011 или десятичное 3, а флаг cf установится в 1, т. е. перенос запомнился в микропроцессоре. Далее программисту нужно будет использовать команду сложения adc, которая учтет перенос из предыдущего разряда. Вычитание неупакованных BCD-чисел Ситуация здесь вполне аналогична сложению. Рассмотрим те же случаи. Пример Результат вычитания не больше 9: 6 = 0000 0110 - 3 = 0000 0011 = 3 = 0000 0011 Как видим, заема из старшей тетрады нет. Результат верный и корректировки не требует. Пример Результат вычитания больше 9: 6 = 0000 0110 - 7 = 0000 0111 = -1 = 1111 1111 Вычитание проводится по правилам двоичной арифметики. Поэтому результат не является BCD-числом. Правильный результат в неупакованном BCD-формате должен быть 9 (0000 1001 в двоичной системе счисления). При этом предполагается заем из старшего разряда, как при обычной команде вычитания, т. е. в случае с BCD числами фактически должно быть выполнено вычитание 16 - 7. Таким образом, видно: как и в случае сложения, результат вычитания нужно корректировать. Для этого существует специальная команда - aas (ASCII Adjust for Substraction) - коррекция результата вычитания для представления в символьном виде. Команда aas также не имеет операндов и работает с регистром al, анализируя его младшую тетраду следующим образом: 1) если ее значение меньше 9, то флаг cf сбрасывается в 0 и управление передается следующей команде; 2) если значение тетрады в al больше 9, то команда aas выполняет следующие действия: а) из содержимого младшей тетрады регистра al (заметьте - не из содержимого всего регистра) вычитает 6; б) обнуляет старшую тетраду регистра al; в) устанавливает флаг cf в 1, тем самым фиксируя воображаемый заем из старшего разряда. Понятно, что команда aas применяется вместе с основными командами вычитания sub и sbb. При этом команду sub есть смысл использовать только один раз, при вычитании самых младших цифр операндов, далее должна применяться команда sbb, которая будет учитывать возможный заем из старшего разряда. Умножение неупакованных BCD-чисел На примере сложения и вычитания неупакованных чисел стало понятно, что стандартных алгоритмов для выполнения этих действий над BCD-числами нет и программист должен сам, исходя из требований к своей программе, реализовать эти операции. Реализация двух оставшихся операций - умножения и деления - еще более сложна. В системе команд микропроцессора присутствуют только средства для производства умножения и деления одноразрядных неупакованных BCD-чисел. Для того чтобы умножать числа произвольной размерности, нужно реализовать процесс умножения самостоятельно, взяв за основу некоторый алгоритм умножения, например "в столбик". Для того чтобы перемножить два одноразрядных BCD-числа, необходимо: 1) поместить один из сомножителей в регистр AL (как того требует команда mul); 2) поместить второй операнд в регистр или память, отведя байт; 3) перемножить сомножители командой mul (результат, как и положено, будет в ах); 4) результат, конечно, получится в двоичном коде, поэтому его нужно скорректировать. Для коррекции результата после умножения применяется специальная команда - aam (ASCII Adjust for Multiplication) - коррекция результата умножения для представления в символьном виде. Она не имеет операндов и работает с регистром АХ следующим образом: 1) делит al на 10; 2) результат деления записывается так: частное в al, остаток в ah. В результате после выполнения команды aam в регистрах AL и ah находятся правильные двоично-десятичные цифры произведения двух цифр. Перед окончанием обсуждения команды aam необходимо отметить еще один вариант ее применения. Эту команду можно применять для преобразования двоичного числа в регистре AL в неупакованное BCD-число, которое будет размещено в регистре ах: старшая цифра результата в ah, младшая - в al. Понятно, что двоичное число должно быть в диапазоне 0... 99. Деление неупакованных BCD-чисел Процесс выполнения операции деления двух неупакованных BCD-чисел несколько отличается от других, рассмотренных ранее операций с ними. Здесь также требуются действия по коррекции, но они должны осуществляться до основной операции, выполняющей непосредственно деление одного BCD-числа на другое BCD-число. Предварительно в регистре ах нужно получить две неупакованные BCD-цифры делимого. Это делает программист удобным для него способом. Далее нужно выдать команду aad - aad (ASCII Adjust for Division) - коррекция деления для представления в символьном виде. Команда не имеет операндов и преобразует двузначное неупакованное BCD-число в регистре ах в двоичное число. Это двоичное число впоследствии будет играть роль делимого в операции деления. Кроме преобразования, команда aad помещает полученное двоичное число в регистр AL. Делимое, естественно, будет двоичным числом из диапазона 0... 99. Алгоритм, по которому команда aad осуществляет это преобразование, состоит в следующем: 1) умножить старшую цифру исходного BCD-числа в ах (содержимое АН) на 10; 2) выполнить сложение АН + AL, результат которого (двоичное число) занести в AL; 3) обнулить содержимое АН. Далее программисту нужно выдать обычную команду деления div для выполнения деления содержимого ах на одну BCD-цифру, находящуюся в байтовом регистре или байтовой ячейке памяти. Аналогично ааш, команде aad можно найти и другое применение - использовать ее для перевода неупакованных BCD-чисел из диапазона 0... 99 в их двоичный эквивалент. Для деления чисел большей разрядности, так же как и в случае умножения, нужно реализовывать свой алгоритм, например "в столбик", либо найти более оптимальный путь. Арифметические действия над упакованными BCD-числами Как уже отмечалось выше, упакованные BCD-числа можно только складывать и вычитать. Для выполнения других действий над ними их нужно дополнительно преобразовывать либо в неупакованный формат, либо в двоичное представление. Из-за того, что упакованные BCD-числа представляют не слишком большой интерес, мы их рассмотрим кратко. Сложение упакованных BCD-чисел Вначале разберемся с сутью проблемы и попытаемся сложить два двузначных упакованных BCD-числа. Пример Сложение упакованных BCD-чисел: 67 = 01100111 + 75 = 01110101 = 142 = 1101 1100 = 220 Как видим, в двоичном виде результат равен 1101 1100 (или 220 в десятичном представлении), что неверно. Это происходит по той причине, что микропроцессор не подозревает о существовании BCD-чисел и складывает их по правилам сложения двоичных чисел. На самом деле, результат в двоично-десятичном виде должен быть равен 0001 0100 0010 (или 142 в десятичном представлении). Видно, что, как и для неупакованных BCD-чисел, для упакованных BCD-чисел существует потребность как-то корректировать результаты арифметических операций. Микропроцессор предоставляет для этого команду daa - daa (Decimal Adjust for Addition) - коррекция результата сложения для представления в десятичном виде. Команда daa преобразует содержимое регистра al в две упакованные десятичные цифры по алгоритму, приведенному в описании команды daa Получившаяся в результате сложения единица (если результат сложения больше 99) запоминается в флаге cf, тем самым учитывается перенос в старший разряд. Вычитание упакованных BCD-чисел Аналогично сложению, микропроцессор рассматривает упакованные BCD-числа как двоичные и, соответственно, выполняет вычитание BCD-чисел как двоичных. Пример Вычитание упакованных BCD-чисел. Выполним вычитание 67-75. Так как микропроцессор выполняет вычитание способом сложения, то и мы последуем этому: 67 = 01100111 + -75 = 10110101 = -8 = 0001 1100 = 28 Как видим, результат равен 28 в десятичной системе счисления, что является абсурдом. В двоично-десятичном коде результат должен быть равен 0000 1000 (или 8 в десятичной системе счисления). При программировании вычитания упакованных BCD-чисел программист, как и при вычитании неупакованных BCD-чисел, должен сам осуществлять контроль за знаком. Это делается с помощью флага CF, который фиксирует заем из старших разрядов. Само вычитание BCD-чисел осуществляется простой командой вычитания sub или sbb. Коррекция результата осуществляется командой das - das (Decimal Adjust for Substraction) - коррекция результата вычитания для представления в десятичном виде. Команда das преобразует содержимое регистра AL в две упакованные десятичные цифры по алгоритму, приведенному в описании команды das. ЛЕКЦИЯ № 19. Команды передачи управления 1. Логические команды Наряду со средствами арифметических вычислений, система команд микропроцессора имеет также средства логического преобразования данных. Под логическими понимаются такие преобразования данных, в основе которых лежат правила формальной логики. Формальная логика работает на уровне утверждений истинно и ложно. Для микропроцессора это, как правило, означает 1 и 0 соответственно. Для компьютера язык нулей и единиц является родным, но минимальной единицей данных, с которой работают машинные команды, является байт. Однако на системном уровне часто необходимо иметь возможность работать на предельно низком уровне - на уровне бит. Рис. 29. Средства логической обработки данных К средствам логического преобразования данных относятся логические команды и логические операции. Операнд команды ассемблера в общем случае может представлять собой выражение, которое, в свою очередь, является комбинаций операторов и операндов. Среди этих операторов могут быть и операторы, реализующие логические операции над объектами выражения. Перед подробным рассмотрением этих средств рассмотрим, что же представляют собой сами логические данные и какие операции над ними производятся. Логические данные Теоретической базой для логической обработки данных является формальная логика. Существует несколько систем логики. Одна из наиболее известных - это исчисление высказываний. Высказывание - это любое утверждение, о котором можно сказать, что оно либо истинно, либо ложно. Исчисление высказываний представляет собой совокупность правил, используемых для определения истинности или ложности некоторой комбинации высказываний. Исчисление высказываний очень гармонично сочетается с принципами работы компьютера и основными методами его программирования. Все аппаратные компоненты компьютера построены на логических микросхемах. Система представления информации в компьютере на самом нижнем уровне основана на понятии бита. Бит, имея всего два состояния (0 (ложно) и 1 (истинно)), естественным образом вписывается в исчисление высказываний. Согласно теории, над высказываниями (над битами) могут выполняться следующие логические операции. 1. Отрицание (логическое НЕ) - логическая операция над одним операндом, результатом которой является величина, обратная значению исходного операнда. Эта операция однозначно характеризуется следующей таблицей истинности (табл. 12). Таблица 12. Таблица истинности для логического отрицания 2. Логическое сложение (логическое включающее ИЛИ) - логическая операция над двумя операндами, результатом которой является "истина" (1), если один или оба операнда имеют значение "истина" (1), и "ложь" (0), если оба операнда имеют значение "ложь" (0). Эта операция описывается с помощью следующей таблицы истинности (табл. 13). Таблица 13. Таблица истинности для логического включающего ИЛИ 3. Логическое умножение (логическое И) - логическая операция над двумя операндами, результатом которой является "истина" (1) только в том случае, если оба операнда имеют значение "истина" (1). Во всех остальных случаях значение операции "ложь" (0). Эта операция описывается с помощью следующей таблицы истинности (табл. 14). Таблица 14. Таблица истинности для логического И 4. Логическое исключающее сложение (логическое исключающее ИЛИ) - логическая операция над двумя операндами, результатом которой является "истина" (1), если только один из двух операндов имеет значение "истина" (1), и ложь (0), если оба операнда имеют значение "ложь" (0) или "истина" (1). Эта операция описывается с помощью следующей таблицы истинности (таб. 15). Таблица 15. Таблица истинности для логического исключающего ИЛИ Система команд микропроцессора содержит пять команд, поддерживающих данные операции. Эти команды выполняют логические операции над битами операндов. Размерность операндов, естественно, должна быть одинакова. Например, если размерность операндов равна слову (16 бит), то логическая операция выполняется сначала над нулевыми битами операндов, и ее результат записывается на место бита 0 результата. Далее команда последовательно повторяет эти действия над всеми битами с первого до пятнадцатого. Логические команды В системе команд микропроцессора есть следующий набор команд, поддерживающих работу с логическими данными: 1) and операнд_1, операнд_2 - операция логического умножения. Команда выполняет поразрядно логическую операцию И (конъюнкцию) над битами операндов операнд_1 и операнд_2. Результат записывается на место операнд_1; 2) ог операнд_1, операнд_2 - операция логического сложения. Команда выполняет поразрядно логическую операцию ИЛИ (дизъюнкцию) над битами операндов операнд_1 и операнд_2. Результат записывается на место операнд_1; 3) хог операнд_1, операнд_2 - операция логического исключающего сложения. Команда выполняет поразрядно логическую операцию исключающего ИЛИ над битами операндов операнд_1 и операнд_2. Результат записывается на место операнде; 4) test операнд_1, операнд_2 - операция "проверить" (способом логического умножения). Команда выполняет поразрядно логическую операцию И над битами операндов операнд_1 и операнд_2. Состояние операндов остается прежним, изменяются только флаги zf, sf, и pf, что дает возможность анализировать состояние отдельных битов операнда без изменения их состояния; 5) not операнд - операция логического отрицания. Команда выполняет поразрядное инвертирование (замену значения на обратное) каждого бита операнда. Результат записывается на место операнда. Для представления роли логических команд в системе команд микропроцессора очень важно понять области их применения и типовые приемы их использования при программировании. С помощью логических команд возможно выделение отдельных битов в операнде с целью их установки, сброса, инвертирования или просто проверки на определенное значение. Для организации подобной работы с битами операнд_2 обычно играет роль маски. С помощью установленных в 1 бите этой маски и определяются нужные для конкретной операции биты операнд_1. Покажем, какие логические команды могут применяться для этой цели: 1) для установки определенных разрядов (бит) в 1 применяется команда ог операнд_1, операнд_2. В этой команде операнд_2, выполняющий роль маски, должен содержать единичные биты на месте тех разрядов, которые должны быть установлены в 1 в операнд_1; 2) для сброса определенных разрядов (бит) в 0 применяется команда and операнд_1, операнд_2. В этой команде операнд_2, выполняющий роль маски, должен содержать нулевые биты на месте тех разрядов, которые должны быть установлены в 0 в операнд_1; 3) команда хог операнд_1, операнд_2 применяется: а) для выяснения того, какие биты в операнд_1 и операнде различаются; б) для инвертирования состояния заданных бит в операнд_1. Интересующие нас биты маски (операнд_2) при выполнении команды хог должны быть единичными, остальные - нулевыми; Для проверки состояния заданных бит применяется команда test операнд_1, операнд_2 (проверить операнд_1). Проверяемые биты операнд_1 в маске (операнд_2) должны иметь единичное значение. Алгоритм работы команды test подобен алгоритму команды and, но он не меняет значения операнд_1. Результатом команды является установка значения флага нуля zf: 1) если zf = 0, то в результате логического умножения получился нулевой результат, т. е. один единичный бит маски, который не совпал с соответствующим единичным битом операнде; 2) если zf = 1, то в результате логического умножения получился ненулевой результат, т. е. хотя бы один единичный бит маски совпал с соответствующим единичным битом операнд_1. Для реакции на результат команды test целесообразно использовать команду перехода jnz метка (Jump if Not Zero) - переход, если флаг нуля zf ненулевой, или команду с обратным действием - jz метка (Jump if Zero) - переход, если флаг нуля zf = 0. Следующие две команды позволяют осуществить поиск первого установленного в 1 бита операнда. Поиск можно произвести как с начала, так и от конца операнда: 1) bsf операнд_1, операнд_2 (Bit Scaning Forward) - сканирование битов вперед. Команда просматривает (сканирует) биты операнд_2 от младшего к старшему (от бита 0 до старшего бита) в поисках первого бита, установленного в 1. Если таковой обнаруживается, в операнд_1 заносится номер этого бита в виде целочисленного значения. Если все биты операнд_2 равны 0, то флаг нуля zf устанавливается в 1, в противном случае флаг zf сбрасывается в 0; 2) bsr операнд_1, операнд_2 (Bit Scaning Reset) - сканирование битов в обратном порядке. Команда просматривает (сканирует) биты операнд_2 от старшего к младшему (от старшего бита к биту 0) в поисках первого бита, установленного в 1. Если таковой обнаруживается, в операнд_1 заносится номер этого бита в виде целочисленного значения. При этом важно, что позиция первого единичного бита слева отсчитывается все равно относительно бита 0. Если все биты операнд_2 равны 0, то флаг нуля zf устанавливается в 1, в противном случае флаг zf сбрасывается в 0. В последних моделях микропроцессоров Intel в группе логических команд появилось еще несколько команд, которые позволяют осуществить доступ к одному конкретному биту операнда. Операнд может находиться как в памяти, так и в регистре общего назначения. Положение бита задается смещением бита относительно младшего бита операнда. Значение смещения может задаваться как в виде непосредственного значения, так и содержаться в регистре общего назначения. В качестве значения смещения вы можете использовать результаты работы команд bsr и bsf. Все команды присваивают значение выбранного бита флагу СЕ 1) bt операнд, смещение_бита (Bit Test) - проверка бита. Команда переносит значение бита в флаг cf; 2) bts операнд, смещение_бита (Bit Test and Set) - проверка и установка бита. Команда переносит значение бита в флаг CF и затем устанавливает проверяемый бит в 1; 3) btr операвд, смещение_бита (Bit Test and Reset) - проверка и сброс бита. Команда переносит значение бита в флаг CF и затем устанавливает этот бит в 0; 4) btc операнд, смещение_бита (Bit Test and Convert) - проверка и инвертирование бита. Команда переносит значение бита в флаг cf и затем инвертирует значение этого бита. Команды сдвига Команды этой группы также обеспечивают манипуляции над отдельными битами операндов, но иным способом, чем логические команды, рассмотренные выше. Все команды сдвига перемещают биты в поле операнда влево или вправо в зависимости от кода операции. Все команды сдвига имеют одинаковую структуру - коп операнд, счетчик_сдвигов. Количество сдвигаемых разрядов - счетчик_сдвигов - располагается на месте второго операнда и может задаваться двумя способами: 1) статически, что предполагает задание фиксированного значения с помощью непосредственного операнда; 2) динамически, что означает занесение значения счетчика сдвигов в регистр cl перед выполнением команды сдвига. Исходя из размерности регистра cl понятно, что значение счетчика сдвигов может лежать в диапазоне от 0 до 255. Но на самом деле это не совсем так. В целях оптимизации микропроцессор воспринимает только значение пяти младших битов счетчика, т. е. значение лежит в диапазоне от 0 до 31. Все команды сдвига устанавливают флаг переноса cf. По мере сдвига битов за пределы операнда они сначала попадают на флаг переноса, устанавливая его равным значению очередного бита, оказавшегося за пределами операнда. Куда этот бит попадет дальше, зависит от типа команды сдвига и алгоритма программы. По принципу действия команды сдвига можно разделить на два типа: 1) команды линейного сдвига; 2) команды циклического сдвига. Команды линейного сдвига К командам этого типа относятся команды, осуществляющие сдвиг по следующему алгоритму: 1) очередной "выдвигаемый" бит устанавливает флаг CF; 2) бит, вводимый в операнд с другого конца, имеет значение 0; 3) при сдвиге очередного бита он переходит во флаг CF, при этом значение предыдущего сдвинутого бита теряется! Команды линейного сдвига делятся на два подтипа: 1) команды логического линейного сдвига; 2) команды арифметического линейного сдвига. К командам логического линейного сдвига относятся следующие: 1) shl операнд, счетчик_сдвигов (Shift Logical Left) - логический сдвиг влево. Содержимое операнда сдвигается влево на количество битов, определяемое значением счетчик_сдвигов. Справа (в позицию младшего бита) вписываются нули; 2) shr операнд, счетчик_сдвигов (Shift Logical Right) - логический сдвиг вправо. Содержимое операнда сдвигается вправо на количество битов, определяемое значением счетчик_сдвигов. Слева (в позицию старшего, знакового бита) вписываются нули. На рисунке 30 показан принцип работы этих команд. Рис. 30. Схема работы команд линейного логического сдвига Команды арифметического линейного сдвига отличаются от команд логического сдвига тем, что они особым образом работаются со знаковым разрядом операнда. 1) sal операнд, счетчик_сдвигов (Shift Arithmetic Left) - арифметический сдвиг влево. Содержимое операнда сдвигается влево на количество битов, определяемое значением счетчик_сдвигов. Справа (в позицию младшего бита) вписываются нули. Команда sal не сохраняет знака, но устанавливает флаг с/в случае смены знака очередным выдвигаемым битом. В остальном команда sal полностью аналогична команде shl; 2) sar операнд, счетчик_сдвигов (Shift Arithmetic Right) - арифметический сдвиг вправо. Содержимое операнда сдвигается вправо на количество битов, определяемое значением счетчик_сдвигов. Слева в операнд вписываются нули. Команда sar сохраняет знак, восстанавливая его после сдвига каждого очередного бита. На рисунке 31 показан принцип работы команд линейного арифметического сдвига. Рис. 31. Схема работы команд линейного арифметического сдвига Команды циклического сдвига К командам циклического сдвига относятся команды, сохраняющие значения сдвигаемых бит. Есть два типа команд циклического сдвига: 1) команды простого циклического сдвига; 2) команды циклического сдвига через флаг переноса cf. К командам простого циклического сдвига относятся: 1) rol операнд, счетчик_сдвигов (Rotate Left) - циклический сдвиг влево. Содержимое операнда сдвигается влево на количество бит, определяемое операндом счетчик_сдвигов. Сдвигаемые влево биты записываются в тот же операнд справа; 2) гог операнд, счетчик_сдвигов (Rotate Right) - циклический сдвиг вправо. Содержимое операнда сдвигается вправо на количество бит, определяемое операндом счетчик_сдвигов. Сдвигаемые вправо биты записываются в тот же операнд слева. Рис. 32. Схема работы команд простого циклического сдвига Как видно из рисунка 32, команды простого циклического сдвига в процессе своей работы осуществляют одно полезное действие, а именно: циклически сдвигаемый бит не только вдвигается в операнд с другого конца, но и одновременно его значение становится значением флага СЕ Команды циклического сдвига через флаг переноса CF отли чаются от команд простого циклического сдвига тем, что сдвигаемый бит не сразу попадает в операнд с другого его конца, а записывается сначала в флаг переноса СЕ Лишь следующее исполнение данной команды сдвига (при условии, что она выполняется в цикле) приводит к помещению выдвинутого ранее бита с другого конца операнда (рис. 33). К командам циклического сдвига через флаг переноса соотносятся следующие: 1) rcl операнд, счетчик_сдвигов (Rotate through Carry Left) - циклический сдвиг влево через перенос. Содержимое операнда сдвигается влево на количество бит, определяемое операндом счетчик_сдвигов. Сдвигаемые биты поочередно становятся значением флага переноса cf. 2) гсг операнд, счетчик_сдвигов (Rotate through Carry Right) - циклический сдвиг вправо через перенос. Содержимое операнда сдвигается вправо на количество бит, определяемое операндом счетчик_сдвигов. Сдвигаемые биты поочередно становятся значением флага переноса СF. Рис. 33. Команды циклического сдвига через флаг переноса CF Из рисунка 33 видно, что при сдвиге через флаг переноса появляется промежуточный элемент, с помощью которого, в частности, можно производить подмену циклически сдвигаемых битов, в частности, рассогласование битовых последовательностей. Под рассогласованием битовой последовательности здесь и далее подразумевается действие, которое позволяет некоторым образом локализовать и извлечь нужные участки этой последовательности и записать их в другое место. Дополнительные команды сдвига Система команд последних моделей микропроцессоров Intel, начиная с i80386, содержит дополнительные команды сдвига, расширяющие возможности, рассмотренные нами ранее. Это - команды сдвигов двойной точности: 1) shld операнд_1, операнд_2, счетчик_сдвигов - сдвиг влево двойной точности. Команда shld производит замену путем сдвига битов операнда операнд_1 влево, заполняя его биты справа значениями битов, вытесняемых из операнд_2 согласно схеме на рис. 34. Количество сдвигаемых бит определяется значением счетчик_сдвигов, которое может лежать в диапазоне 0... 31. Это значение может задаваться непосредственным операндом или содержаться в регистре cl. Значение операнд_2 не изменяется. Рис. 34. Схема работы команды shld 2) shrd операнд_1, операнд_2, счетчик_сдвигов - сдвиг вправо двойной точности. Команда производит замену путем сдвига битов операнда операнд_1 вправо, заполняя его биты слева значениями битов, вытесняемых из операнд_2 согласно схеме на рисунке 35. Количество сдвигаемых бит определяется значением счетчик_сдвигов, которое может лежать в диапазоне 0... 31. Это значение может задаваться непосредственным операндом или содержаться в регистре cl. Значение операнд_2 не изменяется. Рис. 35. Схема работы команды shrd Как мы отметили, команды shld и shrd осуществляют сдвиги до 32 разрядов, но за счет особенностей задания операндов и алгоритма работы эти команды можно использовать для работы с полями длиной до 64 бит. 2. Команды передачи управления Мы познакомились с некоторыми командами, из которых формируются линейные участки программы. Каждая из них в общем случае выполняет некоторые действия по преобразованию или пересылке данных, после чего микропроцессор передает управление следующей команде. Но очень мало программ работает таким последовательным образом. Обычно в программе есть точки, в которых нужно принять решение о том, какая команда будет выполняться следующей. Это решение может быть: 1) безусловным - в данной точке необходимо передать управление не той команде, которая идет следующей, а другой, которая находится на некотором удалении от текущей команды; 2) условным - решение о том, какая команда будет выполняться следующей, принимается на основе анализа некоторых условий или данных. Программа представляет собой последовательность команд и данных, занимающих определенное пространство оперативной памяти. Это пространство памяти может быть либо непрерывным, либо состоять из нескольких фрагментов. То, какая команда программы должна выполняться следующей, микропроцессор узнает по содержимому пары регистров cs:(e)ip: 1) cs - сегментный регистр кода, в котором находится физический (базовый) адрес текущего сегмента кода; 2) eip/ip - регистр указателя команды, в котором находится значение, представляющее собой смещение в памяти следующей команды, подлежащей выполнению, относительно начала текущего сегмента кода. Какой конкретно регистр будет использоваться, зависит от установленного режима адресации use16 или use32. Если указано use 16, то используется ip, если use32, то используется eip. Таким образом, команды передачи управления изменяют содержимое регистров cs и eip/ip, в результате чего микропроцессор выбирает для выполнения не следующую по порядку команду программы, а команду в некотором другом участке программы. Конвейер внутри микропроцессора при этом сбрасывается. По принципу действия команды микропроцессора, обеспечивающие организацию переходов в программе, можно разделить на 3 группы: 1. Команды безусловной передачи управления: 1) команда безусловного перехода; 2) команда вызова процедуры и возврата из процедуры; 3) команда вызова программных прерываний и возврата из программных прерываний. 2. Команды условной передачи управления: 1) команды перехода по результату команды сравнения стр; 2) команды перехода по состоянию определенного флага; 3) команды перехода по содержимому регистра есх/сх. 3. Команды управления циклом: 1) команда организации цикла со счетчиком есх/сх; 2) команда организации цикла со счетчиком есх/сх с возможностью досрочного выхода из цикла по дополнительному условию. Безусловные переходы Предыдущее обсуждение выявило некоторые детали механизма перехода. Команды перехода модифицируют регистр указателя команды eip/ip и, возможно, сегментный регистр кода cs. Что именно должно подвергнуться модификации, зависит: 1) от типа операнда в команде безусловного перехода (ближний или дальний); 2) от указания перед адресом перехода (в команде перехода) модификатора; при этом сам адрес перехода может находиться либо непосредственно в команде (прямой переход), либо в регистре или ячейке памяти (косвенный переход). Модификатор может принимать следующие значения: 1) near ptr - прямой переход на метку внутри текущего сегмента кода. Модифицируется только регистр eip/ip (в зависимости от заданного типа сегмента кода use16 или use32) на основе указанного в команде адреса (метки) или выражения, использующего символ извлечения значения - $; 2) far ptr - прямой переход на метку в другом сегменте кода. Адрес перехода задается в виде непосредственного операнда или адреса (метки) и состоит из 16-битного селектора и 16/32-битного смещения, которые загружаются, соответственно, в регистры cs и ip/eip; 3) word ptr - косвенный переход на метку внутри текущего сегмента кода. Модифицируется (значением смещения из памяти по указанному в команде адресу, или из регистра) только eip/ip. Размер смещения 16 или 32 бит; 4) dword ptr - косвенный переход на метку в другом сегменте кода. Модифицируются (значением из памяти - и только из памяти, из регистра нельзя) оба регистра - cs и eip/ip. Первое слово/двойное слово этого адреса представляет смещение и загружается в ip/eip; второе/третье слово загружается в cs. Команда безусловного перехода jmp Синтаксис команды безусловного перехода - jmp [модификатор] адрес_перехода - безусловный переход без сохранения информации о точке возврата. Адрес_перехода представляет собой адрес в виде метки либо адрес области памяти, в которой находится указатель перехода. Всего в системе команд микропроцессора есть несколько кодов машинных команд безусловного перехода jmp. Их различия определяются дальностью перехода и способом задания целевого адреса. Дальность перехода определяется местоположением операнда адрес_перехода. Этот адрес может находиться в текущем сегменте кода или в некотором другом сегменте. В первом случае переход называется внутрисегментным, или близким, во втором - межсегментным, или дальним. Внутрисегментный переход предполагает, что изменяется только содержимое регистра eip/ip. Можно выделить три варианта внутрисегментного использования команды jmp: 1) прямой короткий; 2) прямой; 3) косвенный. Процедуры В языке ассемблера есть несколько средств, решающих проблему дублирования участков программного кода. К ним относятся: 1) механизм процедур; 2) макроассемблер; 3) механизм прерываний. Процедура, часто называемая также подпрограммой, - это основная функциональная единица декомпозиции (разделения на несколько частей) некоторой задачи. Процедура представляет собой группу команд для решения конкретной подзадачи и обладает средствами получения управления из точки вызова задачи более высокого уровня и возврата управления в эту точку. В простейшем случае программа может состоять из одной процедуры. Другими словами, процедуру можно определить как правильным образом оформленную совокупность команд, которая, будучи однократно описана, при необходимости может быть вызвана в любом месте программы. Для описания последовательности команд в виде процедуры в языке ассемблера используются две директивы: PROC и ENDP. Синтаксис описания процедуры таков (рис. 36). Рис. 36. Синтаксис описания процедуры в программе Из рисунка 36 видно, что в заголовке процедуры (директиве PROC) обязательным является только задание имени процедуры. Среди большого количества операндов директивы PROC следует особо выделить [расстояние]. Этот атрибут может принимать значения near или far и характеризует возможность обращения к процедуре из другого сегмента кода. По умолчанию атрибут [расстояние] принимает значение near. Процедура может размещаться в любом месте программы, но так, чтобы на нее случайным образом не попало управление. Если процедуру просто вставить в общий поток команд, то микропроцессор будет воспринимать команды процедуры как часть этого потока и, соответственно, будет осуществлять выполнение команд процедуры. Условные переходы Микропроцессор имеет 18 команд условного перехода. Эти команды позволяют проверить: 1) отношение между операндами со знаком ("больше - меньше"); 2) отношение между операндами без знака ("выше - ниже"); 3) состояния арифметических флагов ZF, SF, CF, OF, PF (но не AF). Команды условного перехода имеют одинаковый синтаксис: jcc метка_перехода Как видно, мнемокод всех команд начинается с "j" - от слова jump (прыжок), ее - определяет конкретное условие, анализируемое командой. Что касается операнда метка_перехода, то эта метка может находиться только в пределах текущего сегмента кода, межсегментная передача управления в условных переходах не допускается. В связи с этим отпадает вопрос о модификаторе, который присутствовал в синтаксисе команд безусловного перехода. В ранних моделях микропроцессора (i8086, i80186 и i80286) команды условного перехода могли осуществлять только короткие переходы - на расстояние от -128 до +127 байт от команды, следующей за командой условного перехода. Начиная с модели микропроцессора 80386, это ограничение снято, но, как видите, только в пределах текущего сегмента кода. Для того чтобы принять решение о том, куда будет передано управление командой условного перехода, предварительно должно быть сформировано условие, на основании которого и будет приниматься решение о передаче управления. Источниками такого условия могут быть: 1) любая команда, изменяющая состояние арифметических флагов; 2) команда сравнения стр, сравнивающая значения двух операндов; 3) состояние регистра есх/сх. Команда сравнения cmp Команда сравнения стр имеет интересный принцип работы. Он абсолютно такой же, как и у команды вычитания - sub операнде, операнд_2. Команда стр так же, как и команда sub, выполняет вычитание операндов и устанавливает флаги. Единственное, чего она не делает - это запись результата вычитания на место первого операнда. Синтаксис команды стр - стр операнд_1, операнд_2 (compare) - сравнивает два операнда и по результатам сравнения устанавливает флаги. Флаги, устанавливаемые командой стр, можно анализировать специальными командами условного перехода. Прежде чем мы их рассмотрим, уделим немного внимания мнемонике этих команд условного перехода (табл. 16). Понимание обозначений при формировании названия команд условного перехода (элемент в названии команды jcc, обозначенный нами ее) облегчит их запоминание и дальнейшее практическое использование. Таблица 16. Значение аббревиатур в названии команды jccТаблица 17. Перечень команд условного перехода для команды стр операнд_1, операнд_2 He удивляйтесь тому обстоятельству, что одинаковым значениям флагов соответствует несколько разных мнемокодов команд условного перехода (они отделены друг от друга косой чертой в табл. 17). Разница в названии обусловлена желанием разработчиков микропроцессора облегчить использование команд условного перехода в сочетании с определенными группами команд. Поэтому разные названия отражают скорее различную функциональную направленность. Тем не менее то, что эти команды реагируют на одни и те же флаги, делает их абсолютно эквивалентными и равноправными в программе. Поэтому в таблице 17 они сгруппированы не по названиям, а по значениям флагов (условиям), на которые они реагируют. Команды условного перехода и флаги Мнемоническое обозначение некоторых команд условного перехода отражает название флага, с которым они работают, и имеет следующую структуру: первым идет символ "j" (Jump, переход), вторым - либо обозначение флага, либо символ отрицания "n", после которого стоит название флага. Такая структура команды отражает ее назначение. Если символа "n" нет, то проверяется состояние флага, если он равен 1, производится переход на метку перехода. Если символ "n" присутствует, то проверяется состояние флага на равенство 0, и в случае успеха производится переход на метку перехода. Мнемокоды команд, названия флагов и условия переходов приведены в таблице 18. Эти команды можно использовать после любых команд, изменяющих указанные флаги. Таблица 18. Команды условного перехода и флаги Если внимательно посмотреть на таблицы 17 и 18, видно, что многие команды условного перехода в них являются эквивалентными, так как в основе и тех, и других лежит анализ одинаковых флагов. Команды условного перехода и регистр есх/сх Архитектура микропроцессора предполагает специфическое использование многих регистров. К примеру, регистр EAX/AX/AL используется как аккумулятор, а регистры ВР, SP - для работы со стеком. Регистр ЕСХ/СХ тоже имеет определенное функциональное назначение: он выполняет роль счетчика в командах управления циклами и при работе с цепочками символов. Возможно, что функционально команду условного перехода, связанную с регистром есх/сх, правильнее было бы отнести к этой группе команд. Синтаксис этой команды условного перехода таков: 1) jcxz метка_перехода (Jump if ex is Zero) - переход, если сх нуль; 2) jecxz метка_перехода (Jump Equal есх Zero) - переход, если есх нуль. Эти команды очень удобно использовать при организации цикла и при работе с цепочками символов. Нужно отметить ограничение, свойственное команде jcxz/jecxz. В отличие от других команд условной передачи управления команда jcxz/jecxz может адресовать только короткие переходы - на -128 байт или на +127 байт от следующей за ней команды. Организация циклов Цикл, как известно, представляет собой важную алгоритмическую структуру, без использования которой не обходится, наверное, ни одна программа. Организовать циклическое выполнение некоторого участка программы можно, к примеру, используя команды условной передачи управления или команду безусловного перехода jmp. При такой организации цикла все операции по его организации выполняются вручную. Но, учитывая важность такого алгоритмического элемента, как цикл, разработчики микропроцессора ввели в систему команд группу из трех команд, облегчающую программирование циклов. Эти команды также используют регистр есх/сх как счетчик цикла. Дадим краткую характеристику этим командам: 1) loop метка_перехода (Loop) - повторить цикл. Команда позволяет организовать циклы, подобные циклам for в языках высокого уровня с автоматическим уменьшением счетчика цикла. Работа команды заключается в выполнении следующих действий: а) декремента регистра ЕСХ/СХ; б) сравнения регистра ЕСХ/СХ с нулем: если (ЕСХ/СХ) = 0, то управление передается на следующую после loop команду; 2) loope/loopz метка_перехода Команды loope и loopz - абсолютные синонимы. Работа команд заключается в выполнении следующих действий: а) декремента регистра ЕСХ/СХ; б) сравнения регистра ЕСХ/СХ с нулем; в) анализа состояния флага нуля ZF если (ЕСХ/СХ) = 0 или XF = 0, управление передается на следующую после loop команду. 3) loopne/loopnz метка_перехода Команды loopne и loopnz также абсолютные синонимы. Работа команд заключается в выполнении следующих действий: а) декремента регистра ЕСХ/СХ; б) сравнения регистра ЕСХ/СХ с нулем; в) анализа состояния флага нуля ZF: если (ЕСХ/СХ) = 0 или ZF = 1, управление передается на следующую после loop команду. Команды loope/loopz и loopne/loopnz по принципу своей работы являются взаимообратными. Они расширяют действие команды loop тем, что дополнительно анализируют флаг zf, что дает возможность организовать досрочный выход из цикла, используя этот флаг в качестве индикатора. Недостаток команд организации цикла loop, loope/loopz и loopne/loopnz состоит в том, что они реализуют только короткие переходы (от -128 до +127 байт). Для работы с длинными циклами придется использовать команды условного перехода и команду jmp, поэтому постарайтесь освоить оба способа организации циклов. Автор: Цветкова А.В. << Назад: Команды (Команды пересылки данных. Арифметические команды)
▪ История экономических учений. Шпаргалка ▪ Судебная медицина и психиатрия. Шпаргалка ▪ История и теория религий. Шпаргалка
Питомцы как стимулятор разума
06.10.2025 Мини-ПК ExpertCenter PN54-S1
06.10.2025 Глазные капли, возвращающие молодость зрению
05.10.2025
▪ Выражение собачьей морды зависит от внимания человека ▪ 2" дисплей 1280x800 от Japan Display ▪ Ощущение боли оберегает роботов от повреждений ▪ Спортивный автомобиль превращается в скоростной катер
▪ раздел сайта Охрана и безопасность. Подборка статей ▪ статья Джебран Халиль Джебран. Знаменитые афоризмы ▪ статья Какая разница между лягушками и жабами? Подробный ответ ▪ статья Черемуха кистевая. Легенды, выращивание, способы применения ▪ статья Простой программатор микроконтроллеров. Энциклопедия радиоэлектроники и электротехники
Главная страница | Библиотека | Статьи | Карта сайта | Отзывы о сайте www.diagram.com.ua |