Программирование нелинейных алгоритмов 


Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

Программирование нелинейных алгоритмов



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

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

Как вы помните, программа представляет собой последовательность команд и данных, занимающих определенное пространство оперативной памяти. Эта пространство памяти может быть либо непрерывным, либо состоять из нескольких фрагментов.
Ранее нами были рассмотрены средства организации фрагментации кода программы и ее данных на сегменты. То, какая команда программы должна выполняться следующей, микропроцессор узнает по содержимому пары регистров cs:(e)ip:

  • cs — сегментный регистр кода, в котором находится физический (базовый) адрес текущего сегмента кода;
  • eip/ip — регистр указателя команды, в котором находится значение, представляющее собой смещение в памяти следующей команды, подлежащей выполнению, относительно начала текущего сегмента кода.
    Напомню, почему мы записываем регистры eip/ip через косую черту. Какой конкретно регистр будет использоваться, зависит от установленного режима адресации use16 или use32. Если указано use16, то используется ip, если use32, то используется eip.

Таким образом, команды передачи управления изменяют содержимое регистров cs и eip/ip, в результате чего микропроцессор выбирает для выполнения не следующую по порядку команду программы, а команду в некотором другом участке программы. Конвейер внутри микропроцессора при этом сбрасывается.

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

  1. Команды безусловной передачи управления:
    • команда безусловного перехода;
    • вызова процедуры и возврата из процедуры;
    • вызова программных прерываний и возврата из программных прерываний.
  2. Команды условной передачи управления:
    • команды перехода по результату команды сравнения cmp;
    • команды перехода по состоянию определенного флага;
    • команды перехода по содержимому регистра ecx/cx.
  3. Команды управления циклом:
    • команда организации цикла со счетчиком ecx/cx;
    • команда организации цикла со счетчиком ecx/cx с возможностью досрочного выхода из цикла по дополнительному условию.

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

Подобно переменной, транслятор ассемблера присваивает любой метке три атрибута:

  • имя сегмента кода, где эта метка описана;
  • смещение — расстояние в байтах от начала сегмента кода, в котором описана метка;
  • тип метки, или атрибут расстояния.

Последний атрибут может принимать два значения:

  • near — переход на эту метку возможен только в пределах сегмента кода, где эта метка описана. Физически это означает, что для перехода на метку достаточно изменить только содержимое регистра eip/ip;
  • far — переход на эту метку возможен только в результате межсегментной передачи управления, для осуществления которой требуется изменение содержимого как регистра eip/ip, так и регистра cs.

Метку можно определить двумя способами:

  • оператором: (двоеточие);
  • директивой label.

Синтаксис первого способа показан на рис. 10.1.

Рис. 10.1. Синтаксис описания метки оператором:

С помощью этого способа можно определить метку только ближнего типа — near. Символическое имя в программе может быть определено только один раз. Определенную таким образом метку можно использовать в качестве операнда в командах условного перехода jcc и безусловного перехода jmp, call. Эти команды, естественно, должны быть в сегменте кода, где определена метка. Команда ассемблера может находиться как на одной строке с меткой, так и на следующей.
Второй способ определения меток в программе использует директиву label (рис. 10.2).

Рис. 10.2. Синтаксис директивы label

На рис. 10.2 тип метки принимает значения near или far. Обычно директиву label используют для определения идентификатора заданного типа. Например, следующие описания меток ближнего типа эквивалентны:

m1: mov ax,pole_1

и

m1 label near mov ax,pole_1

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

;... public m_far;сделать метку mfar видимой;для внешних программ mfar label far;определение метки дальнего типа m_far mnear:;определение метки ближнего типа n_far mov ax,pole_1...

Определив для команды mov ax,pole_l две метки, можно организовывать переход на эту команду как из данного сегмента команд, так и из других сегментов команд, в том числе принадлежащих другим модулям. Для того чтобы сделать видимым извне имя метки m_fаr, применяется директива publiс. К более подробному описанию этой директивы мы еще вернемся.
Другой часто встречающийся случай применения директивы label — это организация доступа к одной области памяти, как к области, содержащей данные разных типов, например:

;... mas_b label byte mas_w dw 15 dup (?)...;в этом фрагменте оба идентификатора относятся к одной области;памяти и дают возможность работать с ней, используя разные;имена, либо как с байтовым массивом, либо как с массивом слов... mov mas_b+10,al;запись из аl в массив;байтов (в 11-й байт)... mov mas_w,ax;запись из ах в первое;;слово области mas_w...

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

Транслятор ассемблера обеспечивает нам две возможности работы с этим счетчиком:

  • использование меток, атрибуту смещения которых транслятор присваивает значение счетчика адреса той команды, перед которой они появились;
  • применение специального символа $ для обозначения счетчика адреса команд. Этот символ позволяет в любом месте программы использовать численное значение счетчика адреса. Классический пример:
....data;вычисление длины строки в сегменте данных StrMes db "Работаешь на ПК - изучи ассемблер" Len_Msg=$-Str_Mes...

После ассемблирования значение Len_Msg будет равно длине строки, так как значение символа $ в месте его появления отличается от Str_Mes ровно на длину строки.

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

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

К примеру, эту директиву всегда используют при создании исполняемого файла с типом.com. В контексте нашего обсуждения поясним, в чем здесь суть. Мы обсуждали сегментацию и разделение программы на сегменты. Программа в СОМ-формате состоит из одного сегмента величиной не более 64 Кбайт. Сегментные регистры cs и ds содержат одно и то же значение физического адреса, а ss указывает на конец этого единственного сегмента. Программа-загрузчик операционной системы, считывая с диска исполняемые файлы типов.ехе и.com, производит определенные действия. В частности настраивает перемещаемые адреса программы на их конкретные физические значения. Кроме того, к началу каждой исполняемой программы в памяти добавляется специальная область величиной 256 байт (100h) — префикс программного сегмента (PSP). Он предназначен для хранения различной информации о загруженном исполняемом модуле. Для программ формата.com блок PSP находится в начале сегмента размером в 64 Кбайт. В исходной программе, для которой планируется формат исполняемого файла.com, мы должны предусмотреть место для блока PSP, что и делается директивой org 100h.

Чтобы закончить разговор о файлах этого типа, разберемся с тем, как получить исполняемый модуль формата.com. Трансляция программы выполняется как обычно. Далее возможны два варианта действий:

  • использование утилиты Tlink с опцией /t:
    tlink /t имя_объектного_файла
    Этот вариант подходит только в том случае, если вы правильно оформили исходный текст программы для формата. com. Кроме использования директивы org 100h, это предполагает:
    • отсутствие разделения сегментов данных и стека, то есть данные описаны в сегменте кода и для их обхода необходимо использовать команду безусловного перехода jmp;
    • использование директивы assume для указания транслятору на необходимость связать содержимое регистров ds и ss с сегментом кода:
codeseg segment para "code" assume cs: codeseg.ds:codeseg,ss:codeseg org 1OOh jmp ml;здесь описываем данные ml:;далее идут команды программы...
  • использование специальной утилиты exe2bin. Эта утилита позволяет преобразовать уже полученный ранее исполняемый модуль в формате.ехе в формат.com:
    exe2bin имя_файла_ехе имя_файла_сом.соm

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

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

Предыдущее обсуждение выявило некоторые детали механизма перехода. Команды перехода модифицируют регистр указателя команды eip/ip и, возможно, сегментный регистр кода cs. Что именно должно подвергнуться модификации, зависит:

  • от типа операнда в команде безусловного перехода (ближний или дальний);
  • от указания перед адресом перехода (в команде перехода) модификатора; при этом сам адрес перехода может находиться либо непосредственно в команде (прямой переход), либо в регистре или ячейке памяти (косвенный переход).

Модификатор может принимать следующие значения:



Поделиться:


Последнее изменение этой страницы: 2017-02-05; просмотров: 398; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 3.17.74.153 (0.015 с.)