Выражения, операнды и операторы. Операнды

Синтаксис ассемблера

Структура программы на ассемблере

Типы и структура предложений ассемблера

Понятие о метасинтаксических языках

Классификация лексем ассемблера

Описание простых операндов и операндов-выражений

Варианты расположения операндов команд ассемблера

Виды адресации операндов в памяти

Операторы ассемблера

Стандартные директивы сегментации

Упрощенные директивы сегментации

Простые типы данных ассемблера (диапазоны значений)

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

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

Синтаксис ассемблера

Программа на ассемблере представляет собой совокупность блоков памяти, называемых сегментами. Программа может состоять из одного или нескольких таких блоков-сегментов. Сегменты программы имеют определенное назначение, соответствующее типу сегментов: кода, данных и стека. Названия типов сегментов отражают их назначение. Деление программы на сегменты отражает сегментную организацию памяти процессоров Intel (архитектура IA-32). Каждый сегмент состоит из совокупности отдельных строк, в терминах теории компиляции называемых предложениями языка. Для языка ассемблера предложения, составляющие программу, могут представлять собой синтаксические конструкции четырех типов.

Команды (инструкции) представляют собой символические аналоги машинных команд. В процессе трансляции инструкции ассемблера преобразуются в соответствующие команды системы команд процессора.

Макрокоманды - это оформляемые определенным образом предложения текста программы, замещаемые во время трансляции другими предложениями.

Директивы являются указанием транслятору ассемблера на выполнение некоторых действий. У директив нет аналогов в машинном представлении.

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

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

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

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

Имя метки - символьный идентификатор. Значением данного идентификатора является адрес первого байта предложения программы, которому он предшествует.

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

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

Код операции (КОП) и директива - это мнемонические обозначения соответствующей машинной команды, макрокоманды или директивы транслятора.

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

Другой способ описания синтаксиса языка - нормальные (расширенные) формы Бэкуса-Наура. С помощью форм Бэкуса-Наура целевой язык представляется в виде объектов трех типов.

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

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

Правила (формы) описывают порядок формирования конструкций, в том числе предложений, целевого языка.

Каждая форма состоит из трех частей - левой, правой и связки:

Левая часть - всегда нетерминальный символ, который обозначает одну из конструкций языка;

Связка - символ стрелки =>, который можно трактовать как словосочетание «определяется как»;

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

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

Для примера рассмотрим описание и использование форм Бэкуса-Наура для построения десятичных чисел со знаком. Вначале опишем эти формы (правила):

<десятичное_знаковое_целое>=><число_без_знака>|+<число_6ез_знака>|<число_без_ знака>

<число_без_знака>=><дес_цифра> | <число_6ез_знака_цифра>

<дес_цифра>=>0|1|2|3|4|5|6|7|8|9

<десятичное_знаковое_целое>,<число_без_знака>,<дес_цифра> - нетерминальные символы (в исходной программе на ассемблере таких объектов нет);

+|-|0|1|2|3|4|5|б|7|8|9 - терминальные символы (их можно найти в исходном тексте программы), из терминальные символов по приведенным ранее трем правилам строится любое десятичное число;

символ вертикальной черты (|) означает альтернативу при выборе варианта некоторого правила.

Для примера выведем число -501, используя формы Бэкуса-Наура:

<десятичное_знаковое_целое> => <число_без_знака> =>

<число_без_знака><дес_цифра> => <число_без_знака>1 =>

<число_без_знака><дес_цифра>1 => <число_без_знака>01 => <дес_цифра>01 => 501

Предложения ассемблера (см. рис. 5.1-5.3) формируются из лексем, представляющих собой синтаксически неразделимые последовательности допустимых символов языка, имеющие смысл для транслятора.

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

АSСП_символ_буква - все латинские буквы А - Z, а - z, причем прописные и строчные буквы считаются эквивалентными;

decdigit - цифры от 0 до 9;

специальные знаки _, ?, @, $, &;

разделители: „ ., [, ], (,), <, >, {, }, +, /, *, %, !, ", ", ?, \, = #, л.

Лексемами языка ассемблера являются ключевые слова, идентификаторы, цепочки символов и целые числа.

Ключевые слова - это служебные символы языка ассемблера. По умолчанию регистр символов ключевых слов не имеет значения. К ключевым словам относятся:

названия регистров (AL, АН, BL, ВН, CL, СН, DL, ОН, АХ, ЕАХ, ВХ, ЕВХ, СХ, ЕСХ, DX, EDX, ВР, EBP, SP, ESP, DI, EDI, SI, ESI, CS, DS, ES, FS, GS, SS, CRO, CR2, CR3, DRO, DRl, DR2, DR3, DR6,DR7);

операторы (BYTE, SBYTE, WORD, SWORD, DWORD, SDWORD, FWORD, QWORD, TBYTE, REAL4, REALS, REAL10, NEAR16, NEAR32, FAR16, FAR32, AND, NOT, HIGH, LOW, HIGHWORD, LOWWORD, OFFSET, SEG, LROFFSET, TYPE, THIS, PTR, WIDTH, MASK, SIZE, SIZEOF, LENGTH, LENGTHOF, ST, SHORT, TYPE, OPATTR, MOD, NEAR, FAR, OR, XOR, EQ, NE, LT, LE, GT, GE, SHR,

названия команд (КОП) ассемблера, префиксов.

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

=> А5СП_символ_буква | А5СП_символ_буква | А5СП_символ_буква

| |

=> 0| 1 | 2 | 3 |4| 5 | 6| 7| 8| 9

=> _|

Приведенные формы говорят о том, что идентификатор может состоять из одного или нескольких символов. В качестве символов можно использовать буквы латинского алфавита, цифры и некоторые специальные знаки - _, ?, $, @. Идентификатор не может начинаться символом цифры. Длина идентификатора может составлять до 255 символов (247 в MASM), хотя транслятор воспринимает лишь первые 32, а остальные игнорирует. Регулировать длину возможных идентификаторов (в TASM) можно с использованием ключа командной строки /mv. Кроме того, существует возможность указать транслятору на необходимость различать прописные и строчные буквы либо игнорировать их различие (что и делается по умолчанию). Для этого (в TASM) применяются ключи командной строки/mu, /ml,/mx.

Цепочки символов - это последовательности символов, заключенные в одинарные или двойные кавычки. Правила формирования:

=> [[ ]]

|

=> | любой_символ_кроме_кавычки

=> " | "

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

<шестнадц_число> =» <дес_шестнадц_число>h| 0<сим_шестнадц_число>h

<дес_шестнадц_число> =» <сим_шестнадц_число> |

<сим_шестнадц_число> =>

<сим_шестнадц_число>| <дес_шестнадц_число> | |

=> 0| 1 12 | 3|4|5 |6| 7|8|9

= > a | b | c | d | e | f | A | B | C | D | E | F

Важно отметить наличие символов после (h) и перед (0) записью шестнадцатеричного числа. Это сделано для того, чтобы транслятор мог отличить в программе одинаковые по форме записи десятичные и шестнадцатеричные числа. К примеру, числа 1578 и 1578h выглядят одинаково, но имеют разные значения. С другой стороны, какое значение в тексте исходной программы может иметь лексема fe023?

Это может быть и некоторый идентификатор, и, судя по набору символов, шестнадцатеричное число. Для того чтобы однозначно описать в тексте программы на ассемблере шестнадцатеричное число, начинающееся с буквы, его дополняют ведущим нулем «0» и в конце ставят символ «h». Для данного примера правильная запись шестнадцатеричного числа - 0fe023h:

<двоичн_число> => b| <двоичн_число>b

=> 0|1

Для двоичных чисел все просто - после записи нулей и единиц, входящих в их состав, необходимо поставить латинскую букву «b». Пример:

Рассуждениями, приведенными ранее, был показан порядок формирования предложений программы ассемблера и составляющих их элементов (лексем). Также были рассмотрены правила формирования меток, названий команд (префиксов). Осталось обсудить комментарии и операнды. Что касается комментария, то это самый простой элемент предложения ассемблера. Любая комбинация символов ASCII, расположенная в строке за символом точки с запятой (;), транслятором игнорируется, то есть является комментарием. Описанию операндов, ввиду их важности, будет посвящен отдельный подраздел.

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

Итак, начнём с выражений.

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

Это выражение, которое записывает в переменную х число 100.

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

Операнд - это значение, переменная или выражение, которое расположено слева или справа от оператора. Например

Здесь 1 и 2 - это операнды, а знак ПЛЮС (+) - это оператор.

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

Var x: byte; c: char; begin x:= x + c; end.

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

Var x: byte; c: integer; begin x:= x + c; end.

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

Основные виды операторов: арифметические, операторы присваивания, сравнения, логические и поразрядные (побитовые).

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

  • Унарные : когда с оператором используется только один операнд
  • Бинарные : когда с оператором используется два операнда

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

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

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

Оператор + выполняет операцию сложения
Оператор * выполняет операцию умножения

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

Разумеется, компьютер не так прост, чтобы обходиться основными арифметическими операциями: + , - , * , / . Чтобы выполнять сложные (иногда и не очень) программы, этого простого набора не хватает. В этой главе мы рассмотрим операции с данными подробнее.

Прежде всего, что такое операция и операнд? Рассмотрим пример: a + b . a и b —это операнды, т. е. данные, с которыми производится действие; + —операция, т. е. действие, производимое с данными. Если операция проводится с двумя операндами, как в вышеописанном примере, то она называется бинарной ; а в случае типа -a операция - унарная , так как для неё нужен только один операнд.

Примечание: унарные операции всегда записываются перед операндом.

Конструкция из операций и операндов называется выражением.

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

А ещё при помощи операции + можно складывать фрагменты текста—символы и строки. Вот пример, иллюстрирующий это.

program SummingStrings; {Сложение строк}
var
S1, S2: String;
Ch: Char;
begin
S1:="бум";
S2:="ага";
Ch:="!";
WriteLn(S1 + S2); {Можно вместо "+" написать "," - процедура примет строки за отдельные параметры процедуры}
WriteLn(S1 + Ch); {Здесь то же самое}
S1:=S2 + Ch; {А вот тут с запятой ничего не выйдет: текст надо сложить в единое целое операцией "+"}
WriteLn(S1); {Вывод сложенных кусочков, записанных в строке S1 (см. выше)}
ReadLn;
end.

Такие знаки, как + и - , можно использовать в качестве унарных операций. -a при значении a = 10 даёт результат -10 то есть меняет знак перед числом; а +a от обычного a ничем не отличается: числовой знак сохраняется.

Теперь расскажем ещё о нескольких арифметических операциях. Они представляют собой деление несколько другого рода и очень часто применяются для работы с целыми числами. Операция div (от англ. divide разделить) производит деление операндов нацело, т. е. с остатком. А операция mod даёт остаток от деления. Например: 30 div 7 = 4 ; 35 mod 8 = 3 .

Исходя из этих операций, можно сделать такой вывод:
m div n * n + m mod n = m ,
т. е. если результат деления нацело некоторого целого числа m на некоторое целое число n умножить обратно на n , а затем прибавить остаток от деления m на n , мы получим исходное число m .

Теперь рассмотрим другой тип операций, такой, как операции сравнения.

Операции сравнения вам наверняка уже известны из школьной программы (< , > , = и т. д.). Подобные знаки можно использовать и в программировании. Отличие заклучается в том, что сложные знаки, не воспроизводимые клавиатурой, такие как , и , соответственно обозначаются двумя символами: <= , >= , <> .

В зависимости от того, верна ли данная операция сравнения в данном выражении, например m > n , результатом является значение True (если m больше, чем n ) или False (если это не так). Значения m и n должны быть сходных типов, чтобы их можно было сравнить. Например, Integer и Word , Integer и Real , Byte и Real , String и String и т. д..

В следующей программе используются некоторые рассмотренные выше операции сравнения.

program Apples; {Программа "Яблоки"}
const
{За этим словом следует записать константы - постоянные, не меняющиеся в ходе программы числа. Объявление констант необходимо для большего порядка в программе и для её упрощения}
Total = 10; {Объявляемая константа - общее число яблок}
var
Your, {Количество ваших яблок}
Comp: Integer; {Количество яблок компьютера}
begin
{-- Вступительная чатсь --}
WriteLn("Итак, в корзине лежит ", Total, " яблок. Предлагаю разделить их между собой.");
repeat
Write("Сколько яблок Вы возьмёте себе? ");
ReadLn(Your);
if (Your < 0) or (Your > Total) then {Если яблок меньше нуля или больше, чем есть в корзине}
WriteLn("Похоже, Вы ошибаетесь. Как можно взять ", Your, " яблок? Ответьте, пожалуйста, по -нормальному:");
until (Your >= 0) and (Your <= Total); {Повторять вопрос, пока Your не окажется в пределах от 0 до Total, включительно}
{-- Проверка результата деления яблок --}
if Your = 0 then
WriteLn("Зачем мне все ", Total, " яблок? Я же компьютер!")
else
if Your = Total then
WriteLn("Как хотите, забирайте всё. Мне так-то всё равно.")
else
begin
Comp:=Total - Your; {Вычисление количества яблок, доставшихся компьютеру}
WriteLn("Выходит, что у меня будет ", Comp, " яблок.");
if Your = Comp then
WriteLn("Мы разделили их поровну!")
else
if Your > Comp then
WriteLn("Хорошо. Меня устроит.")
else
if Your < Comp then
WriteLn("Что-то многовато Вы мне оставили, мне столько не надо.");
end;
ReadLn;
end.

В этой программе вместе с операциями сравнения попутно рассмотрен раздел описания констант const . В данном случае объявлена константа Total , заменяющая общее число яблок (10 ) во всей программе. Теперь изменение этого числа не потребует столько времени, сколько заняла бы замена всех десяток в программе: достаточно исправить значение константы Total .

Логические операции - это операции, основанные на простейших приёмах логики. Эти операции уже использовались ранее, но подробному описанию не подвергались. Мы рассмотрим их применение с логическими выражениями (таких, как сравнение) или операндами (значения типа Boolean ).

Выражение с бинарной операцией and (и ) принимает значение True , если и первое, и второе выражения истинны. Например:

if (a = 10) and (b > 0) then
WriteLn("Выражение истинно");

Этот фрагмент выводит текст "Выражение истинно", если a = 10 и b > 0 .

Операция or (или ) даёт результат True , если истинно хотя бы одно из выражений-операндов. Например, условие (a * b = c) or (a + b = c) выполнится, если сумма и/или произведение a и b будет равно c .

Операция xor (исключающее или ) даёт результат True , если истинен только один из его операндов. Например:

if (a + 1 > b) xor (a > b - 1) then
WriteLn("Этот текст вы не увидите");

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

Унарная операция not (не ) "переворачивает" операнд - делает его значение прямо противоположным. not True - то же, что и False , not False аналогично True . Вот ещё несколько примеров.

if not (St = "выход") then
Sum:=Sum + St;
...
if not StopSumming then
I:=I + 1;

Чаще всего операция not применяется в случаях, аналогичных второму, когда имеется переменная типа Boolean и надо выполнить оператор/цикл, если её значение ложно.

Это основной набор операций, которые могут вам не раз пригодиться. Конечно, есть и другие: @ , shl , shr . Но мы рассмотрим их позже, когда это потребуется.

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

· регистр - регистр;

· регистр - память;

· память - регистр;

· непосредственный операнд - регистр;

· непосредственный операнд - память.

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

Для приведенных ранее правил сочетания типов операндов есть исключения, которые касаются:

§ команд работы с цепочками, которые могут перемещать данные из памяти в память;

§ команд работы со стеком, которые могут переносить данные из памяти в стек, также находящийся в памяти;

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

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

Рассмотрим классификацию операндов, поддерживаемых транслятором ассем­блера.

§ Операнд задается неявно на микропрограммном уровне. В этом случае команда явно не содержит операндов. Алгоритм выполнения команды использует не­которые объекты по умолчанию (регистры, флаги в EFLAGS и т. д.). Например, команды CLI и STI неявно работают с флагом прерывания IF в регистре EFLAGS, а команда XLAT неявно обращается к регистру AL и строке в памяти по адресу, определяемому парой регистров DS:BX.

o Операнд задается в самой команде (непосредственный операнд). Это может быть число, строка, имя или выражение, имеющее некоторое фиксированное (кон­стантное) значение. Физически непосредственный операнд находится в коде команды, то есть является ее частью. Для его хранения в команде выделяется поле длиной до 32 битов (см. главу 3). Непосредственный операнд может быть только вторым операндом (источником). Операнд-приемник может находить­ся либо в памяти, либо в регистре.

Например, команда mov ax,0ffffh пересылает в регистр АХ шестнадцатеричную константу 0ffffh. Команда add sum,2 складыва­ет содержимое поля по адресу sum с целым числом 2 и записывает результат по месту первого операнда, то есть в память. Если непосредственный операнд имя, то оно не должно быть перемещаемым, то есть зависеть от адреса за­грузки программы в память. Такое имя можно определить оператором EQU или =.


num equ 5 ; вместо num ассемблер везде подставляет 5

Imd = num-2 ; вместо num ассемблер везде подставляет 3

mov al, num ;эквивалентно mov al,5, здесь 5-непосредственный операнд

add , imd ;сложение := +3.

;здесь imd - непосредственный операнд

mov al, 5 ;al:=5, здесь 5 - непосредственный операнд

В данном фрагменте определяются две константы, которые затем используются в качестве непосредственных операндов и командах пересылки MОV и сложе­ния ADD.

· Адресные операнды задают физическое расположение операнда в памяти путем указания двух составляющих адреса: сегмента и смещения (рис. 5.4). К примеру:

mov ax, dx: 0000h ; записать слово в ах из области памяти

; по физическому адресу 0000: 0000

Здесь третья команда MOV имеет адресный операнд.

рис. 5.4. Синтаксис адресных операндов

· Перемещаемые операнды - любые символьные имена, представляющие неко­торые адреса памяти. Эти адреса могут обозначать местоположение в памяти некоторой инструкции (если операнд - метка) или данных (если операнд - имя области памяти в сегменте данных). Перемещаемые операнды отличаются от адресных тем, что они не привязаны к конкретному адресу физической па­мяти. Сегментная составляющая адреса перемещаемого операнда неизвестна и определяется после загрузки программы в память для выполнения.

К примеру:

mas_w dw 25 dup (0)

…………….

……………..

lea si, mas_w ; mas_w - перемещаемый операнд

В этом фрагменте mas_w - символьное имя, значением которого является адрес первого байта области памяти размером 25 слов. Полный физический адрес этой области памяти будет известен только после загрузки программы в на­мять для выполнения.

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

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

jmp $+3 ; безусловный переход на команду mov

сld ; длина команды сld составляет 1 байт

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

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

□ 32-разрядные регистры ЕАХ, ЕВХ. ЕСХ, EDX, ESI, EDI, ESP, EBP;

□ 16-разрядные регистры АХ, ВХ, СХ, DX, SI, DI, SP, ВР;

□ 8-разрядные регистры АН, AL, BH, BL, СН, CL, DH, DL;

□ сегментные регистры CS, D5, SS, ES, FS, GS;

□ системные регистры CR0, CR2, CR3, CR4, DR0, DR1, DR2, DR3, DR6, DR7 (см. описание команды MOV в приложении).

Например, команда add ax,bx складывает содержимое регистров АХ и ВХ и запи­сывает результат в ВХ. Команда dec si уменьшает содержимое SI на 1.

И еще пример:

mov al, 4 ;константу 4 заносим в регистр al

mov dl, pass + 4 ;байт по адресу pass+4 - в регистр dl

add al, dl ;команда с регистровыми операндами

□ Операнд - порт ввода-вывода. Помимо адресного пространства оперативной памяти процессор поддерживает адресное пространство ввода-вывода, которое используется для доступа к устройствам ввода-вывода. Объем адресного про­странства ввода-вывода составляет 64 Кбайт. Для любого устройства компью­тера в этом пространстве выделяются адреса. Конкретное значение адреса в пре­делах этого пространства называется портом ввода-вывода. Физически порту ввода-вывода соответствует аппаратный регистр (не путать с регистром процессора), доступ к которому осуществляется с помощью специальных команд ассемблера IN и OUT. Например,

in al,60h; ввести байт из порта 60h

Регистры, адресуемые с помощью порта ввода-вывода, могут иметь разрядность 8,16 или 32 бита, но для конкретного порта разрядность регистра фиксирована. Команды IN и OUT работают с фиксированной номенклатурой объектов. В каче­стве источника информацииили получателя применяются так называемыерегистры-аккумуляторы ЕАХ, АХ, AL. Выбор регистра определяется разрядностьюпорта. Номер порта может задаваться непосредственнымоперандом в коман­дах IN и OUT или значением в регистре DX. Последний способ позволяет динами­чески определить номер порта в программе. Например:

mov dx,20h ; записать номер порта 20h в регистр dx

mov al,20h ; записать значение 20h в регистр al

out dx.al ; вывести значение 20h в порт 20Н

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

Записи (аналогично структурному типу) используются для доступа к битово­муполю некоторой записи (глава 13).

Операнд находится в стеке.

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

Последнийвариант расположения операндов, ввиду его важности и большого объема, рассмотрим более подробно. Обсуждение будет сопровождаться примера­ми команд ассемблера, цель которых - демонстрация того, как изменяется фор­мат команды ассемблера при применении того или иного вида адресации. В связи с этим вернемся еще раз к рис. 2.8 (см. главу 2), который иллюстрирует принцип формирования физического адреса на адресной шине процессора. Видно, что ад­рес операнда формируется как сумма двух составляющих - сдвинутого на 4 бита содержимого сегментного регистра и 16-разрядного эффективного адреса, кото­рый в общем случае вычисляется как сумма трех компонентов: базы, смещения и индекса.