Оглавление
Выбор размера данных ----------------------------------------------------------------- В языке Ассемблера с помощью инструкции MOV можно копировать байты или значения размером в слово. Давайте рассмотрим, каким образом Турбо Ассемблер определяет, с каким размером данных нужно работать. Во многих случаях операнды явным образом указывают Турбо Ас- семблеру, каким должен быть размер данных. Если в инструкции ис- пользуется регистр, то размер данных должен соответствовать раз- меру этого регистра. Например, размер данных в следующих инструк- циях ясен: . . . mov al,1 ; байт mov dx,si ; слово mov bx,[dl] ; слово mov [bp+si+2],al ; байт . . . Аналогично, именованные ячейки памяти имеют заданные разме- ры, поэтому размер данных в следующих инструкциях для Турбо Ас- семблера ясен: . . . .DATA TestChar DB ? TempPointer DW TestChar . . . .CODE . . . mov [TestChar],'A' mov [TempPointer],0 . . . Однако, иногда приходится иметь дело с инструкциями MOV, в которых размер данных не определен. Например, Турбо Ассемблер не может сделать вывод о том, следует ли в следующей инструкции за- писать значение размером в слово или в байт: mov [bx],1 Фактически, Турбо Ассемблер не будет знать, как такую инст- рукцию нужно ассемблировать. Было бы удобно иметь возможность временного доступа к переменой размером в слово, как к байту, и наоборот. Турбо Ассемблер дает вам способ гибкого определения размера данных в виде операций WORD PTR и BYTE PTR. Операция WORD PTR указывает Турбо Ассемблеру, что данный операнд в памяти нужно ин- терпретировать, как операнд размером в слово, а операция BYTE PTR указывает Турбо Ассемблеру, что данный операнд в памяти нужно ин- терпретировать, как операнд размером в байт, независимо от его предопределенного размера. Например, можно сделать так, что в последнем примере значение 1 размером в слово будет записываться в слово, на которое указывает регистр BX: mov WORD PTR [bx],1 или же можно сделать так, что в данном примере значение 1 разме- ром в байт будет записываться в байт, на который указывает ре- гистр BX: mov BYTE PTR [bx],1 Заметим, что операции WORD PTR и BYTE PTR, если их применять к регистрам, не имеют смысла, так как регистры всегда имеют фик- сированный размер. В таком случае операции BYTE PTR и WORD PTR игнорируются. Аналогично, эти операции игнорируются при примене- нии к константам, поскольку они всегда имеют тот же размер, что и операнд-приемник. Операции WORD PTR и BYTE PTR имеют другое назначение: их можно использовать для временного выбора размера данных для име- нованной переменной в памяти. Почему это может оказаться полезным? Рассмотрим следующий пример: . . . .DATA Source1 DD 12345h Source2 DD 54321h Sum DD ? . . . .CODE . . . mov ax,WORD PTR [Source1] ; получить младшее ; слово Source1 mov dx,WORD PTR [Source1+2] ; получить старшее ; слово Source1 add ax,WORD PTR [Source2] ; прибавить к Source2 ; младшее слово adc dx,WORD PTR [Source2+2] ; прибавить к Source2 ; старшее слово mov WORD PTR [Sum],ax ; сохранить младшее ; слово суммы mov WORD PTR [Sum+2],dx ; сохранить старшее ; слово суммы . . . Все переменные, которые используются в данном примере, представляют собой длинные целые или двойные слова. Однако, про- цессор 8086 не может выполнять сложение двойных слов непосредс- твенно, поэтому такое сложение приходится разбивать на ряд опера- ций со словами. Операция WORD PTR позволяет обращаться к частям переменных Source1, Source2 и Sum, как к словам, хотя сами эти переменные представляют собой двойные слова. Операции FAR PTR и NEAR PTR хотя и не влияют непосредственно на размер данных, они аналогичны операциям WORD PTR и BYTE PTR. Операция FAR PTR приводит к тому, что целевая метка инструкции перехода или вызова будет интерпретироваться, как дальняя метка, и при этом будут загружаться оба регистра CS и IP. С другой сто- роны, операция NEAR PTR вынуждает интерпретировать соответствую- щую метку, как метку ближнего типа, переход на которую осуще- ствляется путем загрузки одного регистра IP. Данные со знаком и без знака ----------------------------------------------------------------- И числа со знаком, и беззнаковые числа состоят из последова- тельности двоичных цифр. Ответственность за различие этих двух видов чисел возлагается на программиста, который пишет программу на Ассемблере (то есть на вас), а не на процессор 8086. Например, значение 0FFFFh может представлять собой либо 65535, либо -1, в зависимости от того, как ваша программа его интерпретирует. Откуда вы знаете, что 0FFFFh - это -1? Прибавьте к нему 1: . . . mov ax,0ffffh add ax,1 . . . и вы обнаружите, что результат будет равен 0. Как раз такой ре- зультат должен получиться при сложении -1 и 1. Одна и та же инструкция ADD будет работать одинаково хорошо, независимо от того, представляют ли собой операнды значения со знаком или беззнаковые значения. Предположим, например, что вы вычли из 0FFFFh значение -1 следующим образом: . . . mov ax,offffh sub ax,1 . . . Результат при этом был бы равен 0FFFEh, что представляет со- бой 65534 (беззнаковое число) или -2 (число со знаком). Если это кажется непонятным, прочитайте одну из книг, реко- мендуемых в конце данного руководства (или одну из книг по Ас- семблеру, изданных в СССР, например книгу Бредли). Это позволит вам больше узнать об арифметике с дополнением до двух - средстве, с помощью которого процессор 8086 обрабатывает числа со знаком. К сожалению, мы не располагаем здесь местом, чтобы подробно расска- зать об арифметике значений со знаком, хотя для программиста это представляет собой одну из важных тем, которую нужно хорошо пони- мать. Пока же запомните, что инструкции ADD, SUB, ADC и SBB рабо- тают одинаково хорошо как с беззнаковыми значениями, так и со значениями со знаком, поэтому для таких операций не требуется специальных инструкций сложения или сочетания. Знак имеет значе- ние в операциях умножения или деления (как вы увидите далее), а также при преобразовании размеров данных. TASM2 #1-5/Док = 204 =
Оглавление
Группа директив для указания типа процессора
Директивы указания типа процессора задают набор используемых инструкций.
.186, .286, .386, .486, .586, .686 — использование инструкций процессоров 80186, 80286, 80386, 80486, Pentium, Pentium Pro
.286P, .386P, .486P, .586P, .686P — использование инструкций процессоров, включая инструкции защищенного режима
.287, .387 — использование инструкций математического сопроцессора
Пример кода:
; по-умолчанию в начале ставится режим .8086 .model small .stack 100h .code .486 ; указываем режим 80486, чтобы иметь возможность использовать команду BSWAP start: mov edx, 01020304h bswap edx mov ax, 4C00h int 21h end start
Директивы для указания сегментов
Директива SEGMENT
name SEGMENT [align] [combine] [use] [‘class’]
Определяет сегмент с заданным именем name. Если сегмент с таким именем уже был определен ранее, то данный сегмент интерпретируется как продолжение предыдущего.
Необязательные параметры:
align — определяет выравнивание начального адреса сегмента на границу, определяемую значением параметра. Возможные значения:
BYTE | выравнивание не выполняется. Сегмент может начинаться с любого адреса памяти |
WORD | выравнивание на границу слова (2 байта) |
DWORD | выравнивание на границу двойного слова (4 байта) |
PARA | выравнивание по границе параграфа (16 байт). Используется по-умолчанию. |
PAGE | выравнивание на границу в 256 байт |
combine — определяет, как сегменты с одним и тем же именем, но из различных модулей должны комбинироваться во время компоновки. Возможные значения:
PUBLIC | заставляет компоновщик соединить все сегменты с одинаковым именем. Новый объединенный сегмент будет целым и непрерывным. Все адреса (смещения) объектов будут вычисляться относительно начала этого нового сегмента |
STACK | выполняется конкатенация всех сегментов с одним и тем же именем для формирования одного непрерывного сегмента, затем регистр SS инициализируется значением начала сегмента, а SP — длиной сегмента. Если не указано ни одного сегмента стека, компоновщик выдаст предупреждение, что стековый сегмент не найден. Если сегмент стека создан, а комбинированный тип STACK не используется, программист должен явно загрузить в регистр SS адрес сегмента (подобно тому, как это делается для регистра DS) |
COMMON | данный сегмент и все другие сегменты с этим именем помещаются по одному адресу. Все сегменты с данным именем будут перекрываться и совместно использовать память. Размер полученного в результате сегмента будет равен размеру самого большого сегмента |
AT address | сегмент помещается по абсолютному адресу параграфа address |
PRIVATE | данный сегмент не комбинируется с другими сегментами. Используется по-умолчанию |
use — определяет разрядность сегмента. Возможные значения:
USE16 | сегмент с 16-разрядной адресацией. Максимальный размер сегмента 64 Кб |
USE32 | сегмент с 32-разрядной адресацией. Максимальный размер сегмента 4 Гб. В модели памяти FLAT используется по-умолчанию |
class — задает строковое значение «класса» сегмента. Компоновщик объединяет вместе в памяти все сегменты с одним и тем же именем класса.
Директива ENDS
ENDS
Определяет конец сегмента.
Директива ASSUME
ASSUME register:segment[,register:segment][, register:segment]…
Задает сегментный регистр, который будет использоваться для вычисления действующего адреса для всех меток и переменных, определенных для сегмента или группы сегментов с указанным именем.
Пример кода:
.386 stack16 segment stack db 100 dup(0) ends data16 segment var db 0 ends code16 segment use16 'code' assume cs:code16, ds:data16, ss:stack16 start: mov var,00h mov ax,4C00h int 21h ends end start
Директивы для упрощенного указания сегментов
Директива .MODEL
.MODEL memory-model [, language-type] [, stack-option]
Задает модель памяти для упрощенных директив определения сегментов.
Параметры:
memory-model — модель памяти. Возможные значения:
TINY | Код, данные и стек объединены в одну группу с именем DGROUP и размером до 64 Кб. Используется для создания программ формата .com. Некоторые языки эту модель не поддерживают. СS=DS=SS=DGROUP |
SMALL | Код занимает один сегмент, данные и стек объединены в одну группу с именем DGROUP (хотя для описания могут использоваться разные сегменты). Эту модель обычно используют для большинства программ на ассемблере. CS=_text DS=SS=DGROUP |
MEDIUM | Код занимает несколько сегментов, по одному на каждый объединяемый программный модуль. Все ссылки на передачу управления — типа far (вызов подпрограмм). Данные и стек объединены в одной группе DGROUP; все ссылки на них — типа near (для доступа к данным используется только смещение). CS=<модуль>_text DS=SS=DGROUP |
COMPACT | Код находится в одном сегменте, данные и стек в группе DGROUP и могут занимать несколько сегментов, так что для обращения к данным требуется указывать сегмент и смещение (ссылка на данные — типа far). CS=_text DS=SS=DGROUP |
LARGE | Код может занимать несколько сегментов, по одному на каждый объединяемый программный модуль. Стек и данные находятся в группе DGROUP. Для ссылки на данные используются дальние указатели -far. CS=<модуль>_text DS=SS=DGROUP |
HUGE | То же, что модель LARGE |
FLAT | То же, что и модель TINY, но для 32-битных сегментов |
language-type — тип языка программирования. Возможные значения:
C | Аргументы передаются через стек, справа налево. Стек очищает вызывающая программа. |
PASCAL, BASIC | Аргументы передаются через стек, слева направо. Стек очищает вызываемая подпрограмма. |
STDCALL | Аргументы передаются через стек, справа налево. Стек очищает вызываемая подпрограмма. |
stack-option — организация стека. Возможные значения для 16 бит: NEARSTACK, FARSTACK. Для 32 бит не используется. Указание NEARSTACK группирует сегменты стека в один физический сегмент (DGROUP) вместе с данными. Регистр сегмента стека SS назначается на тот же адрес что и регистр сегмента данных DS. FARSTACK не группирует стек с сегментом данных DGROUP; таким образом регистр SS не будет равен регистру DS.
Директива .CODE или CODESEG
.CODE [имя]
Определяет начало сегмента кода. Если задали среднюю или большую модель памяти, то за директивой может следовать необязательное имя, которое указывает имя сегмента. По-умолчанию имя сегмента _TEXT.
Директива .DATA или DATASEG
.DATA
Определяет начало инициализированного сегмента данных.
Директива .DATA?
.DATA?
Определяет в модуле начало неинициализированного сегмента данных.
Директива .CONST
.CONST
Определяет начало сегмента данных-констант.
Сегменты .DATA, .DATA?, .CONST помещаются в одну группу с именем DGROUP
Директива .STACK или STACK
.STACK [размер]
Определяет начало сегмента стека, выделяя количество байт, заданное параметром. Если размер не указывается, выделяется 1024 байт.
Пример кода:
.model small,stdcall .stack 100h .data str db 'Hello, world!',0 .code start: mov ax,4C00h int 21h end start
Группа директив для резервирования памяти
DB, DW, DD, DF, DP, DQ, DT — Резервирование памяти с размером соответственно 1 байт (DB), 2 байта (DW), 4 байта (DD), 6 байт (DF, DP), 8 байт (DQ) и 10 байт (DT)
[имя] DB выражение[, выражение][, выражение]…
Резервирует область памяти, заданного директивой размера, с указанным именем, и инициализирует значением выражения. Выражение может быть числом, строкой символов, специальным символом «?«, а также выражением с использованием директивы DUP.
счетчик DUP (выражение[, выражение]…)
Повторяет операцию выделения памяти для указанных данных столько раз, сколько задано значением счетчика
Пример кода:
val1 dw ? val2 db 10 dup(0) val3 dd 4 dub(0FFFFFFFFh) val4 db 'Test string',0 val5 db 2 dup(0Fh), 4 dup(?)
Директива STRUC (STRUCT)
[имя] STRUC
Определяет структуру данных с заданным именем, содержащую поля. В каждом поле для определения его размера используются обычные директивы выделения данных (DB, DW и т.д.). Поля структуры могут быть именованными или нет.
Директива ENDS
[имя] ENDS
Определяет конец структуры.
Пример кода:
str1 struc db 10h field1 dw ? field2 db 5 dup(?) str1 ends
Директива UNION
[имя] UNION
Определяет объединение структур данных, имеющих одно и то же имя. Объединение означает, что структуры будут располагаться по одному и тому же адресу в памяти. Закрытие объединений делается так же как и для структур — с помощью директивы ENDS.
Пример кода:
str1 union db ? db ? db 2 dup(0) str1 ends str2 union dw ? dw ? str2 ends
Группа директив модификации размера указателей
Используются когда возникает неоднозначность в размере операнда команды. Например когда в ячейку памяти записывается число, то компилятор не может определить число какого размера требуется записать, и в этом случае требуется укзать директиву модификации размера.
BYTE [PTR] — Приводит адресное выражение к размеру в байт
DWORD [PTR] — Приводит адресное выражение к размеру в двойное слово (4 байта)
FAR [PTR] — Приводит к тому, что адресное выражение будет дальним указателем
FWORD [PTR] — Приводит к тому, что адресное выражение будет иметь размер 32-разрядного дальнего указателя
NEAR [PTR] — Приводит к тому, что адресное выражение будет ближним указателем на код
PWORD [PTR] — Приводит к тому, что адресное выражение будет иметь размер 32-разрядного дальнего указателя
QWORD [PTR] — Приводит к тому, что адресное выражение будет иметь размер четверного слова (8 байт)
SHORT — Приводит к тому, что выражение будет указателем на код короткого типа (в границах -128 до +127 байт от текущего адреса программы)
TBYTE [PTR] — Приводит к тому, что адресное выражение будет иметь размер 10 байт
WORD [PTR] — Приводит адресное выражение к размеру в слово (2 байта)
Пример кода:
mov es:[di], dword ptr 0
Директивы определения процедур
Директива PROC
имя PROC [язык] [расстояние] [, аргумент] [, аргумент] …
Определяет начало процедуры с указанным именем.
Необязательные параметры:
язык — определяет, из какого языка выполняется вызов для доступа к данной процедуре: C, PASCAL, BASIC, STDCALL или NOLANGUAGE. Этим определяются соглашения по именам идентификаторов, порядок аргументов в стеке и то, останутся ли аргументы в стеке при возврате управления из процедуры (см. директиву MODEL). Если язык не задан, то используется язык заданный в директиве MODEL.
расстояние — это значения NEAR или FAR. Оно определяет тип инструкций RET или RETF, которые будут использоваться в процедуре.
аргумент — параметр процедуры в формате имя[:тип], где имя — имя параметра, тип — тип параметра. В качестве параметра можно задать массив в виде имя[N]:тип. Параметры будут доступны в процедуре через положительные смещения относительно регистра BP. Компилятор автоматически преобразует обращения к параметру по имени в соответствующие смещения относительно BP.
Директива ENDP
[имя] ENDP
Определяет окончание процедуры
Директива USES
USES элемент[, элемент]…
Показывает, какие регистры или элементы данных, состоящие из одной лексемы, вы хотите занести в стек в начале охватывающей процедуры. Перед возвратом управления из процедуры эти регистры будут извлекаться из стека. Вы должны использовать эту директиву перед первой инструкцией, которая генерирует в процедуре реальный код.
Директива LOCAL
LOCAL элемент[, элемент]…[=идентификатор]
В процедуре директива LOCAL определяет имена, которые доступны в стеке через отрицательные смещения относительно регистра BP. Если указан идентификатор, то ему присваивается количество байт, выделенных на локальные переменные (размер всего блока локальных переменных в байтах).
Пример кода:
.386 .model small, stdcall .stack 100h .code test proc Param1:word, Param2[3]:word uses ax, bx local LocVar:word = @size mov ax, @size mov ax, LocVar mov ax, Param1 mov ax, Param2[2] ret test endp start: mov ax, 4C00h int 21h end start
Директивы для макроопределений
Директива MACRO
имя MACRO [параметр][, параметр]…
Определяет начало макроопределения с указанным именем. У макроопредения могут быть заданы необязательные параметры, которые будут использоваться при подстановке тела макроопределения в текст программы.
При использовании макроопределения в программе, в параметры можно передавать строковые выражения, которые будут подставляться в тело макроопределения. Если передаваемая строка содержит пробелы или какие-то символы, вроде запятых, точек, то параметр можно заключить в угловые скобки <…>.
Пример кода:
.386 .model small, stdcall .stack 100h .data var dw 0 .code MAddr macro sreg,reg,addr mov reg, seg addr mov sreg,reg mov reg, offset addr endm start: MAddr ds,di,var mov ax, 4C00h int 21h end start
Директива ENDM
ENDM
Определяет окончание макроопределения
Директива REPT
REPT выражение
Повторяет блок операторов, заданный между директивами REPT и ENDM столько раз, сколько задается выражением. Блок операторов должен заканчиваться директивой ENDM.
Директива IRP
IRP параметр, аргумент[. аргумент]…
Повторяет блок операторов, заданный между директивой IRP и ENDM со строковой подстановкой. Аргументами может быть любой текст: символы, строки, числа и т.д. Для каждого указанного аргумента ассемблирование блока операторов выполняется только один раз. При каждом ассемблировании блока для каждого вхождения «параметра» в операторах подставляется следующий аргумент в списке.
Пример кода:
irp reg,ax,bx push reg endm ; после ассемблирования получится следующий код push ax push bx
Другие директивы
Директива COMMENT
COMMENT [символ ограничитель] … [символ ограничитель]
Позволяет задать многострочный комментарий, ограниченный с начала и с конца заданным символом-ограничителем.
Пример кода:
comment * Несколько строк комментария *
Директива EQU
имя EQU выражение
Определяет имя как строку, псевдоним или число, содержащие результат вычисления выражения.
Пример кода:
var equ 4000h mov ax, var ; то же самое, что mov ax,4000h
Директива END
END метка
Отмечает конец исполняемого модуля и задает начальный адрес, с которого будет исполняться программа.
Директива EVEN
EVEN
Округляет счетчик адреса до следующего четного адреса
Директива SEG
SEG выражение
Возвращается адрес сегмента выражения со ссылкой на память
Директива OFFSET
OFFSET выражение
Возвращает смещение выражения в текущем сегменте (или в группе, которой принадлежит сегмент, если используются упрощенные директивы определения сегментов).
Директива ORG
ORG выражение
Устанавливает счетчик инструкций в текущем сегменте в соответствии с адресом, задаваемым выражением.
Директива RADIX
RADIX основание
Задает основание системы счисления для целочисленных констант (2, 8, 10 или 16)
Директива SIZE
SIZE имя
Возвращает размер элемента данных, выделенного для переменной
Пример кода:
.data var dw 4 dup(0) .code mov ax,size var ;ax = 8
.data
num dd 090F0433H
.code
mov ax, @data
mov ds, ax
mov ax, word ptr num
mov bx, word ptr num+2
mov cl, byte ptr num+1
For mov ax, word ptr num, AH = 04, AL = 33.
Why? Can someone explain to me how to figure this out?
asked Feb 18, 2017 at 12:59
1
num dd 090F0433H
This defines a dword in memory. Since x86 uses little endianness, the lowest byte of this dword will be stored at the lowest address. You chose to name this lowest address «num».
In memory:
33h,04h,0Fh,09h
^
|
num points here
mov ax, word ptr num
When you wrote this mov ax, word ptr num
you effectively asked to retrieve only the lowest word (2 bytes) at the «num» address.
You got the 1st byte 33h in AL
and the 2nd byte 04h in AH
, combined in one register: AX=0433h
.
mov bx, word ptr num+2
This one works similarly but will instead give only the highest word.
You’ll get BX=090Fh
mov cl, byte ptr num+1
Here you asked to retrieve only the 2nd byte at the «num» address.
You’ll get CL=04h
.
answered Feb 18, 2017 at 14:48
FifoernikFifoernik
9,7291 gold badge20 silver badges27 bronze badges
Содержание
Введение в Ассемблер. Работа с регистрами. Адресация и команды пересылки данных. Арифметические операции с целыми числами
Цели:
-
закрепить знания о регистрах общего назначения 32-разрядных процессоров INTEL;
-
научиться использовать косвенную адресацию для работы с оперативной памятью;
-
научиться использовать команды умножения и деления целых чисел.
Основная нагрузка при работе компьютера ложится на процессор и память. Процессор выполняет команды, хранящиеся в памяти. В памяти хранятся также и данные. Между процессором и памятью происходит непрерывный обмен информацией. Процессор имеет свою небольшую память, состоящую из регистров. Команда процессора, использующая находящиеся в регистрах данные, выполняется много быстрее аналогичных команд над данными в памяти. Поэтому часто для того, чтобы выполнить какую-либо команду, данные для неё предварительно помещают в регистры. Результат команды можно при необходимости поместить обратно в память. Обмен данными между памятью и регистрами осуществляют команды пересылки. Кроме этого, можно обмениваться данными между регистрами, посылать и получать данные от внешних устройств. В регистр и ячейку памяти можно посылать и непосредственный операнд – число. Кроме этого имеются команды, с помощью которых можно помещать и извлекать данные из стека – специальной области памяти, используемой для хранения адресов возврата из функций, передаваемых в функцию параметров и локальных переменных.
Адресация и выделение памяти
Для процессора вся память представляет собой последовательность однобайтовых ячеек, каждая из которых имеет свой адрес. Для того, чтобы оперировать большими числами, пары ячеек объединяют в слова, пары слов – в двойные слова, пары двойных слов – в учетверенные слова. Чаще всего в программах оперируют байтами, словами и двойными словами (в соответствии с одно-, двух- и четырехбайтовыми регистрами процессоров). Адресом слова и двойного слова является адрес их младшего байта.
На листинге 1 представлен пример доступа к памяти при помощи косвенной адресации. Рассмотрим подробно. Прежде всего, отметим, что в программу включен заголовочный файл <windows.h>, который содержит заголовки всех основных API-функций ОС Windows, а также определение большого количества структур, типов переменных (в частности, определение типа DWORD, который сводится просто к unsigned int). В ассемблерных командах используются переменные, определенные средствами языка Си. Это связано с тем, что встроенный в Си ассемблер не позволяет осуществлять резервирование памяти. Адресация памяти с помощью переменных называют также прямой адресацией. Косвенная адресация состоит в следующем. Если адрес ячейки содержится в регистре, например, EAX, то для того, чтобы послать туда число 100, нужно написать MOV BYTE PTR [EAX], 100. Префикс BYTE PTR указывает, что в операции участвует однобайтовая ячейка памяти (можно использовать WORD PTR, DWORD PTR – это будет соответствовать двух- и четырехбайтовому операнду). Чтобы получить адрес ячейки памяти, используется команда LEA.
/* использование косвенной адресации */ /* подключаемые заголовочные файлы */ #include <stdio.h> // необходим для работы printf #include <conio.h> // необходим для работы _getch(); #include <windows.h> // содержит определение типов BYTE, WORD, DWORD; /* глобальные переменные */ BYTE a=10; // 8-битное беззнаковое целое число DWORD addressRet; // переменная для хранения адреса /* главная функция */ void main() { __asm { LEA EAX, a; // загрузка эффективного адреса переменной a в регистр EAX // (в 32-разрядной ОС адрес ячейки памяти занимает 4 байта, // поэтому для хранения адреса надо использовать расширенные // регистры) MOV addressRet, EAX; // помещаем в переменную addressRet адрес переменной // а, хранящийся в регистре EAX. Обратите внимание: // этот адрес меняется при каждом запуске программы MOV BYTE PTR [EAX], 100; // помещаем по адресу, хранящемуся в регистре EAX // число 100 - фактически, присваиваем переменной а // значение 100 }; printf("address of variable a is %un", addressRet); // выводим адрес переменной a printf("value of variable a = %un", a); // выводим значение переменной а _getch(); }
Листинг 1.
Здесь используется доступ к переменной типа BYTE по указателю – структура BYTE PTR [EAX]. Немного позже мы увидим, как этот прием используется при написании программ.
Задания.
-
Попробуйте записать по адресу переменной а, хранящемуся в регистре ЕАХ, число 260. Какой ответ вы получили? Почему?
-
Задайте переменную b типа WORD и переменную c типа DWORD. Используя косвенную адресацию, запишите в эти переменные числа 1023 и 70000, соответственно.
-
Поместите в переменную с число 70000, используя указатель типа BYTE:
LEA EAX, c; MOV BYTE PTR [EAX], 70000;
Объясните полученный результат (напоминаем, что адресом слова или двойного слова является адрес их младшего байта). Проделайте то же самое, используя указатель типа WORD.
-
На листинге 2 представлена программа, иллюстрирующая способы доступа к переменным по указателям. Наберите эту программу. Разберитесь с комментариями. Попробуйте поменять элементы массива. Попробуйте выводить результаты в шестнадцатеричной системе (вместо %u в строке формата функции printf() используйте %x).
/* использование косвенной адресации */ #include <stdio.h> // необходим для работы printf #include <conio.h> // необходим для работы _getch(); #include <windows.h> // содержит определение типов BYTE, WORD, DWORD; BYTE ar[6] = {1, 12, 128, 50, 200, 10}; // статический массив типа BYTE BYTE a1, a2, a3, a4, a5; // 8-битные беззнаковые числа WORD b1, b2; // 16-битные беззнаковые числа DWORD c; // 32-битное беззнаковое число void main() { __asm { LEA EBX, ar; // загрузка эффективного адреса первого элемента массива // ar в регистр EAX MOV AL, BYTE PTR [EBX]; // помещаем в регистр AL число (типа BYTE) // число, записанное по адресу, хранящемуся // в регистре EBX, то есть первый элемент массива MOV a1, AL; // записываем содержимое регистра AL в переменную a /*помещаем в переменную a2 число, записанное по адресу "начало массива плюс 1 байт", то есть по адресу второго элемента массива*/ MOV AL, BYTE PTR [EBX] + 1; MOV a2, AL; /*помещаем в переменную a3 число, записанное по адресу "число, записанное в регистре EBX плюс 1", то есть по адресу второго элемента массива*/ MOV AL, BYTE PTR [EBX+1] ; MOV a3, AL; /*помещаем в переменную a4 число, записанное по адресу "номер, хранящийся в регистре EDX, начиная с номера, записанного регистре EBX", то есть второй элемент массива*/ MOV EDX, 1; MOV AL, BYTE PTR [EBX][EDX]; MOV a4, AL; /*помещаем в переменную a5 число, записанное по адресу "сумма чисел, записанных в регистрах EBX и EDX", то есть второй элемент массива*/ MOV AL, BYTE PTR [EBX+EDX]; MOV a5, AL; /*помещаем в переменную b1 2 и 1 элементы массива*/ MOV AX, WORD PTR [EBX]; MOV b1, AX; /*помещаем в переменную b2 4 и 3 элементы массива*/ MOV AX, WORD PTR [EBX]+2; MOV b2, AX; /*помещаем в переменную с 6, 5, 4 и 3 элементы массива*/ MOV EAX, DWORD PTR [EBX]+2; MOV c, EAX; }; printf("first element of array a1 = %u n", a1); printf("second element of array a2 = %u n", a2); printf("second element of array (another way) a3 = %u n", a3); printf("second element of array (base addressation) a4 = %u n", a4); printf("second element of array (base addr. - another way) a5 = %u n", a5); printf("1, 2 elements of array b1 = %u n", b1); printf("3, 4 elements of array b2 = %u n", b2); printf("3, 4, 5, 6 elements of array c = %u n", c); _getch(); }
Листинг 2.
Доступ к переменной по указателю используется и в языках высокого уровня (очень часто – при создании динамических массивов).
Указатель – это переменная, которая содержит адрес другой переменной (говорят, что указатель указывает на переменную того типа, адрес которой он содержит). Существует одноместная (унарная, т.е. для одного операнда) операция взятия адреса переменной & (амперсанд, как в названии мультфильма Tom&Jerry). Если имеем объявление int a, то можно определить адрес этой переменной: &a. Если Pa – указатель, который будет указывать на переменную типа int, то можно записать: Pa=&a. Существует унарная операция * (она называется операцией разыменования), которая действует на переменную, содержащую адрес объекта, т.е. на указатель. При этом извлекается содержимое переменной, адрес которой находится в указателе. Если Pa=&a, то, воздействуя на обе части операцией * получим (по определению этой операции): *Pa=a. Исходя из этого, указатель объявляется так:
<тип переменной> * <имя указателя>
Это и есть правило объявления указателя: указатель на переменную какого-то типа – это такая переменная, при воздействии на которую операцией разыменования получаем значение переменной того же типа. На листинге 3 приведен пример использования указателя в языке Си.
/* получение адреса переменной - сравнение С и Assembler */ #include <stdio.h> // необходим для работы printf #include <conio.h> // необходим для работы _getch(); #include <windows.h> // содержит определение типов BYTE, WORD, DWORD; BYTE a=10; BYTE *cAddr; DWORD asmAddr; BYTE b; void main() { __asm { LEA EBX, a; // загрузка эффективного адреса переменной a в регистр EBX MOV asmAddr, EBX; // помещаем в переменную asmAddr содержимое регистра EBX, // т.е. адрес переменной a }; cAddr=&a; // записываем в переменную типа BYTE* адрес переменной типа BYTE b=*cAddr; // осуществляем разыменование указателя на переменную а printf("Assembler: address of a is %un", asmAddr); printf("C: address of a is %un", cAddr); printf("C: value of a is %un", b); _getch(); }
Листинг 3.
На листинге 4 представлена программа, позволяющая получать адреса элементов массивов разных типов средствами Cи. Обратите внимание на значения соседних адресов элементов массива.
/* адресация в массивах */ #include <stdio.h> // необходим для работы printf #include <conio.h> // необходим для работы _getch(); #include <windows.h> // содержит определение типов BYTE, WORD, DWORD; unsigned int mas[4]; // массив 4-байтовых целых чисел unsigned int *ptrMas; // указатель на переменную типа unsigned int unsigned short int masShort[4]; // массив 2-байтовых целых чисел unsigned short int *ptrMasShort; // указатель на переменную типа unsigned short int BYTE masBYTE[4]; // массив 1-байтовых целых чисел BYTE *ptrMasBYTE; // указатель на переменную типа BYTE void main() { ptrMas = mas; // помещаем в указатель адрес первого элемента массива ptrMasShort = masShort; ptrMasBYTE = masBYTE; printf("array of int n"); for(int i=0; i<4; i++) printf("int pointer+%u = %un", i, ptrMas+i); printf("narray of short int n"); for(int i=0; i<4; i++) printf("short pointer+%u = %un", i, ptrMasShort+i); printf("narray of BYTE n"); for(int i=0; i<4; i++) printf("byte pointer+%u = %un", i, ptrMasBYTE+i); _getch(); }
Листинг 4.
Один из наиболее часто встречающихся случаев – использование указателей для динамического выделения памяти при создании массивов (листинг 5).
/* динамическое выделение памяти */ #include <stdio.h> // необходим для работы printf #include <conio.h> // необходим для работы _getch() #include <windows.h> // содержит определение типов BYTE, WORD, DWORD #include <stdlib.h> // необходим для работы malloc() #include <malloc.h> // необходим для работы malloc() void main() { int* ptint; // указатель на переменную типа int /* Выделяем память под массив. Аргумент функции malloc() - число байт. Нам нужен массив из 10 целых чисел. Поэтому общее число байт - размер числа типа int (определяется функцией sizeof()), умноженный на число элементов массива. Стоящая перед malloc() конструкция (int*) осуществляет приведение к типу int* (то есть теперь выделенная память будет рассматриваться компилятором как совокупность 4 байтных ячеек, в которых хранятся числа типа int) */ ptint = (int*)malloc(10 * sizeof(int)); /* заполняем массив */ for(int i=0; i<10; i++) ptint[i]=i; /*выводим элементы массива*/ for(int i=0; i<10; i++) printf("%d ",ptint[i]); free(ptint); // освобождаем память _getch(); }
Листинг 5.
Задание. Выведите на экран адреса элементов массива, созданного в программе, показанной на листинге 5. Попробуйте создать динамический массив типа double, заполнить его, вывести на печать элементы массива и их адреса.
Арифметические операции над целыми числами
Сложение и вычитание целых чисел
Рассмотрим 3 основные команды сложения. Команда INC осуществляет инкремент, т.е. увеличение содержимого операнда на 1, например, INC EAX. Команда INC устанавливает флаги OF, SF, ZF, AF, PF в зависимости от результатов сложения.
Команда ADD осуществляет сложение двух операндов. Результат пишется в первый операнд (приемник). Первый операнд может быть регистром или переменной. Второй операнд – регистром, переменной или числом. Невозможно, однако, осуществлять операцию сложения одновременно над двумя переменными. Команда действует на флаги CF, OF, SF, ZF, AF, PF. Её можно использовать для знаковых и для беззнаковых чисел.
Команда ADC осуществляет сложение двух операндов подобно команде ADD и флага (бита) переноса. С её помощью можно осуществлять сложение чисел, размер которых превышает 32 бита или изначально длина операндов превышает 32 бита.
/* сложение целых чисел */ #include <stdio.h> // необходим для работы printf() #include <conio.h> // необходим для работы _getch() #include <windows.h> // содержит определение типов BYTE, WORD, DWORD; int a,b,c; DWORD d,e,f,m,n,l,k; void main() { a=100; b=-200; f=0; d=0xffffffff; e=0x00000010; m=0x12345678; n=0xeeeeeeee; l=0x11111111; k=0x22222222; __asm{ /* сложение положительного и отрицательного чисел */ MOV EAX, a; ADD EAX, b; MOV c, EAX; /* сложение двух больших чисел */ MOV EAX, e; // EAX = 0x00000010 ADD d, EAX; // результат превышает 4 байта, поэтому флаг CF // устанавливается в 1: // 0xffffffff // + 0x00000010 // ---------- // 0x0000000f (и 1 должна переноситься в следующий разряд, // но его нет, поэтому устанавливается флаг CF) ADC f, 0; // осуществляет сложение двух операндов (подобно команде ADD) и // флага (бита) переноса CF. Вначале f=0, второй операнд также 0, // поэтому в данном случае выполнение команды сводится к помещению в // переменную f значения CF /* сложение двух больших чисел, расположенных в паре регистров */ MOV EDX, m; // поместили в EDX старшие 4 байта первого числа, //EDX=0x12345678 MOV EAX, n; // поместили в EAX младшие 4 байта первого числа, // EAX=0xeeeeeeee MOV ECX, l; // поместили в ECX старшие 4 байта второго числа, // ECX=0x11111111 MOV EBX, k; // поместили в EBX младшие 4 байта первого числа, // EBX=0x22222222 ADD EAX, EBX; // сложили младшие 4 байта MOV n, EAX; ADC EDX, ECX; // сложили старшие 4 байта MOV m, EDX; }; printf("c=a+b=%dn",c); printf("f=d+e=%x%xn",f,d); printf("sum of lowest 4 bytes = %xn",n); printf("sum of highest 4 bytes = %xn",m); _getch(); }
Листинг 6.
/*вычитание целых чисел*/ #include <stdio.h> // необходим для работы printf() #include <conio.h> // необходим для работы _getch() #include <windows.h> // содержит определение типов BYTE, WORD, DWORD; int a,b,c; __int64 i,j,k; void main() { a=100; b=-200; i=0x1ffffffff; j=0x1fffffffb; __asm{ /* вычитание 32-битных чисел */ MOV EAX, a; SUB EAX, b; MOV c, EAX; /* вычитание 64-битных чисел */ MOV EAX, DWORD PTR i; // поместили в EAX адрес младших 4 байт числа i. // По этому адресу записано число 0xffffffff MOV EDX, DWORD PTR i+4; // поместили в EDX адрес старших 4 байт числа i. // По этому адресу записано число 0x00000001 MOV EBX, DWORD PTR j; // поместили в EBX адрес младших 4 байт числа j. // По этому адресу записано число 0xfffffffb MOV ECX, DWORD PTR j+4; // поместили в ECX адрес старших 4 байт числа j. // По этому адресу записано число 0x00000001 SUB EAX, EBX; // вычитаем из младших 4 байт числа i младшие 4 байта // числа j. Эта операция влияет на флаг CF SBB EDX, ECX; // вычитаем из старших 4 байт числа i старшие 4 байта // числа j, а также флаг CF MOV DWORD PTR k, EAX; // помещаем в память младшие 4 байта результата MOV DWORD PTR k+4, EDX; // помещаем в память старшие 4 байта результата }; printf("c=a+b=%dn",c); printf("k=i-j=%I64xn",k);// интерпретируем выводимое число как __int64 _getch(); }
Листинг 7.
Умножение целых чисел
В отличие от сложения и вычитания умножение чувствительно к знаку числа, поэтому существует две команды умножения: MUL – для умножения беззнаковых чисел, IMUL – для умножения чисел со знаком.
Единственным оператором команды MUL может быть регистр или переменная. Здесь важен размер этого операнда (источника).
-
Если операнд однобайтовый, то он будет умножаться на AL, соответственно, результат будет помещен в регистр AX независимо от того, превосходит он один байт или нет. Если результат не превышает 1 байт, то флаги OF и CF будут равны 0, в противном случае – 1.
-
Если операнд двухбайтовый, то он будет умножаться на AX, и результат будет помещен в пару регистров DX:AX (а не в EAX, как могло бы показаться логичным). Соответственно, если результат поместится целиком в AX, т.е. содержимое DX будет равно 0, то нулю будут равны и флаги CF и OF.
-
Наконец, если оператор-источник будет иметь длину четыре байта, то он будет умножаться на EAX, а результат должен быть помещен в пару регистров EDX:EAX. Если содержимое EDX после умножения окажется равным нулю, то нулевое значение будет и у флагов CF и OF.
Команда IMUL имеет 3 различных формата. Первый формат аналогичен команде MUL. Остановимся на двух других форматах.
IMUL operand1, operand2
operand1 должен быть регистр, operand2 может быть числом, регистром или переменной. В результате выполнения умножения (operand1 умножается на operand2, и результат помещается в operand1) может получиться число, не помещающееся в приемнике. В этом случае флаги CF и AF будут равны 1 (0 в противном случае).
IMUL operand1, operand2, operand3
В данном случае operand2 (регистр или переменная) умножается на operand3 (число) и результат заносится в operand1 (регистр). Если при умножении возникнет переполнение, т.е. результат не поместится в приемник, то будут установлены флаги CF и OF. Применение команд умножения приведено на листинге 8.
#include <stdio.h> // необходим для работы printf() #include <conio.h> // необходим для работы _getch() #include <windows.h> // содержит определение типов BYTE, WORD, DWORD; DWORD a=100000; __int64 b; int c=-1000; int e; void main() { __asm{ /* беззнаковое умножение */ MOV EAX, 100000; // поместили в EAX число, превышающее 2 байта MUL DWORD PTR a; // умножаем содержимое регистра EAX на a, // результат будет помещен в пару регистров // EDX:EAX MOV DWORD PTR b, EAX; // помещаем в младшие 4 байта // 8-байтной переменной b младшие 4 байта результата MOV DWORD PTR b+4, EDX; // помещаем в старшие 4 байта // 8-байтной переменной b старшие 4 байта результата /* знаковое умножение */ IMUL EAX, c, 1000; // умножаем с на 1000 и результат помещаем в EAX MOV e, EAX; // помещаем результат умножения в переменную e }; printf("a*100000 = %I64dn",b);// интерпретируем выводимое число как __int64 printf("e = %dn",e); _getch(); }
Листинг 8. Применение команд умножения
Деление целых чисел
Деление беззнаковых чисел осуществляется с помощью команды DIV. Команда имеет только один операнд – это делитель. Делитель может быть регистром или ячейкой памяти. В зависимости от размера делителя выбирается и делимое.
-
Делитель имеет размер 1 байт. В этом случае делимое помещается в регистре AX. Результат деления (частное) содержится в регистре AL, в регистре AH будет остаток от деления.
-
Делитель имеет размер 2 байта. В этом случае делимое помещается в паре регистров DX:AX. Результат деления (частное) содержится в регистре AX, в регистре DX будет остаток от деления.
-
Делитель имеет размер 4 байта. В этом случае делимое помещается в паре регистров EDX:EAX. Результат деления (частное) содержится в регистре EAX, в регистре EDX будет остаток от деления.
Команда знакового деления IDIV полностью аналогична команде DIV. Существенно, что для команд деления значения флагов арифметических операций не определены. В результате деления может возникнуть либо переполнение, либо деление на 0. Обработку исключения должна обеспечить операционная система.
#include <stdio.h> // необходим для работы printf() #include <conio.h> // необходим для работы _getch() #include <windows.h> // содержит определение типов BYTE, WORD, DWORD; DWORD a,b,c; void main() { a=100000; // делимое - 4 байта __asm{ /* беззнаковое деление */ MOV EAX, a; // поместили младшие 4 байта делимого в регистр EAX MOV EDX, 0; // поместили старшие 4 байта делимого в регистр EDX MOV EBX, 30; // поместили в EBX делитель (4 байта!) DIV EBX; // выполнили деление содержимого EDX:EAX на // содержимое EBX MOV b, EAX; // помещаем в b частное MOV c, EDX; // помещаем в c остаток }; printf("div b = %dn",b); printf("mod c = %dn",c); _getch(); }
Листинг 9. Применение команд деления
Литература
Команда
MOV пересылает содержимое источника
(второго операнда) на место приёмника
(первого операнда) (табл. 5.10). Флаги
команда не изменяет. Пересылаемая
величина извлекается из команды, регистра
или ячейки памяти, а записывается в
регистр или ячейку памяти. Пересылать
одной командой двойное слово запрещено.
Инструкции
с кодами операций 8C и 8E обеспечивают
загрузку и извлечение информации из
сегментных регистров. Занесение
информации в регистр CS запрещено, т.к.
регистровая пара CS:IP определяет адрес
команды, которая должна быть выполнена
следующей. Поэтому для этой цели
необходимо использовать любую инструкцию
дальнего (межсегментного) перехода (см.
раздел 5.5).
Табл. 10. КомандаMOV.
Код |
Инструкция |
Описание |
88 /r |
MOV r/m8, r8 |
Пересылка из r8 в |
89 /r |
MOV r/m16, r16 |
Пересылка из r16 в |
8A /r |
MOV r8, r/m8 |
Пересылка из r/m8 в |
8B /r |
MOV r16, r/m16 |
Пересылка из r/m16 в |
8C /r |
MOV r/m16, Sreg |
Пересылка из Sreg в |
8E /r |
MOV Sreg, r/m16 |
Пересылка из r/m16 в |
A0 ow |
MOV AL, m8 |
Пересылка из m8 в AL. |
A1 ow |
MOV AX, m16 |
Пересылка из m16 в |
A2 ow |
MOV m8, AL |
Пересылка из AL в m8. |
A3 ow |
MOV m16, AX |
Пересылка из AX в |
B0+rb |
MOV r8, imm8 |
Пересылка imm8 в r8. |
B8+rw |
MOV r16, imm16 |
Пересылка imm16 в r16. |
C6 /0 |
MOV r/m8, imm8 |
Пересылка imm8 в r/m8. |
C7 /0 |
MOV r/m16, imm16 |
Пересылка imm16 в |
Командой
MOV запрещена также пересылка из одной
ячейки памяти в другую, из одного
сегментного регистра в другой и запись
непосредственного операнда в сегментный
регистр. Если по алгоритму такое действие
требуется выполнить, оно реализуется
путем пересылки данных через несегментный
регистр.
Например,
записать число 100 в сегментный регистр
DS можно так:
MOV АХ, 100
MOV DS, AX
Как
правило, в команде MOV легко определить
тип одного из операндов и размер
пересылаемой величины, например:
MOV АХ, 300 ;
пересылка слова
MOV AH, AL ;
пересылка байта
В ряде
случаев по операндам команды MOV нельзя
определить размер пересылаемой величины.
Пусть в регистре AX находится адрес
некоторой ячейки памяти и требуется
записать 0 в эту ячейку. Такое обнуление
можно сделать с помощью команды:
MOV [AX], 0
Однако
по этой команде нельзя определить,
какого размера пересылаемый ноль,
поскольку второй операнд может обозначать
ноль размером в байт (00h) или размером в
слово (0000h). Кроме того, адрес из регистра
AX может быть также адресом ячейки как
размером в байт, так и размером в слово
(с одного и того же адреса могут начинаться
ячейки разных размеров). Поэтому ассемблер
зафиксирует ошибку, сообщая, что типы
операндов неизвестны.
Для
явного указания типов операндов команды
используется оператор указания типаPTR:
<тип> PTR
<выражение>
где <тип> – BYTE, WORD
или DWORD, выражение может быть константным
или адресным.
Если
указано константное выражение, то
оператор сообщает, что значение выражения
(число) должно рассматриваться как
величина указанного типа (размера);
например, BYTE PTR 0 – ноль как байт, а WORD
PTR 0 – ноль как слово.
Если в
PTR указано адресное выражение, то оператор
сообщает, что адрес, являющийся значением
выражения, должен восприниматься
ассемблером как адрес ячейки указанного
типа, например: WORD PTR A – адрес A обозначает
слово (байты с адресами A и A+1).
Вернемся
к нашему примеру и запишем его корректно
с использованием оператора PTR. При
обнулении байта по адресу из регистра
AX, то команда имеет вид:
MOV
BYTE PTR [AX], 0 или MOV [AX], BYTE PTR
0
Если
требуется переслать нулевое слово:
MOV
WORD PTR [AX], 0 или MOV [AX], WORD PTR
0
Обычно
принято уточнять тип операнда-адреса,
а не тип непосредственного операнда.
Перестановку
двух величин можно реализовать с помощью
команды MOV, однако существует и специальная
команда XCHG (табл. 11). Инструкция меняет
местами содержимое своих операндов.
Флаги команда не меняет.
Табл. 11. Команда
XCHG.
Код |
Инструкция |
Описание |
90+rw |
XCHG AX, r16 |
AX ↔ r16 |
90+rw |
XCHG r16, AX |
r16 ↔ AX |
86 /r |
XCHG r/m8, r8 |
r/m8 ↔ r8 |
86 /r |
XCHG |
r8 ↔ r/m8 |
87 /r |
XCHG r/m16, r16 |
r/m16 ↔ r16 |
87 /r |
XCHG r16, r/m16 |
r16 ↔ r/m16 |
Например,
для обмена содержимого регистров AX и
BX следует записать:
MOV АХ, 124
MOV BX, 43
ХСHG AX, BX ;
АХ=43, BX=124
Перестановка
содержимого двух ячеек памяти недопустима.
Если требуется провести такую перестановку,
она реализуется через регистр. Следующий
пример меняет местами значения байтовых
переменных X и Y:
MOV AL, X ; AL=X
XCHG AL, Y ; AL=Y, Y=X
MOV X, AL ; X=Y
(исходное значение)
Рассмотрим
еще несколько примеров команды MOV:
A DW 10 DUP(0) ;
одномерный массив A из 10-ти элементов
размером слово
B DW A ; в
переменной B хранится адрес переменной
A
…
MOV BX, B ;
помещение в регистр BX адреса 1-ого
элемента
MOV DX, [BX] ;
помещение в регистр DX значения переменной
A
MOV AX, BX ;
помещение в регистр AX адреса 1-ого
элемента A
…
MOV BX, 4 ; индекс
второго элемента массива
MOV DX, A[BX] ;
модификация адреса по регистру BX
Модификация
адреса может быть выполнена и для
двумерного массива. Например, для матрицы
10×10:
A DW 10 DUP(10 DUP
(0))
запись элемента [i,j] в
регистр AX будет следующей (регистры SI
и DI содержат индексы):
MOV AX, A[SI][DI]
Следует
заметить, что следующие записи эквиваленты
(A и B – некоторые регистры-модификаторы):
[A][B] = [A]+[B] =
[A+B]
В
рассмотренном примере для получения
адреса переменной A мы ввели дополнительную
переменную B. Однако для загрузки
исполнительного адреса операнда в
памяти существует специальная команда
LEA (табл. 12). Полученный результат заносится
в регистр общего назначения. Флаги
команда не изменяет. В качестве первого
операнда команды должен быть указан
регистр общего назначения, а в качестве
второго – адресное выражение.
Табл. 12. Команда LEA.
Код |
Инструкция |
Описание |
8D /r |
LEA r16, m |
Загрузка в r16 |
Команда
LEA может ссылаться на операнд источника
с помощью любого типа адресации, который
можно указать байтом ModRegR/M. Поля Mod и R/M
байта ModRegR/M используются при вычислении
адреса. Поле Reg этого байта определяет
регистр общего назначения, в который
должен быть занесён адрес.
Во
многих случаях команда LEA идентична
команде MOV с непосредственным операндом.
Например, следующие две команды выполняют
одинаковое действие:
MOV BX, OFFSET A
LEA BX, A
В первом
случае выполняется непосредственная
пересылка, которая использует смещение
переменной A относительно начала сегмента
данных. Оператор OFFSET сообщает транслятору,
что в регистр BX надо загрузить смещение
адресного значения переменной A. Команда
LEA вычисляет действительный адрес
переменной A и помещает его в регистр
BX.
Для
загрузки в регистр BX адреса десятого
байта массива, на который указывает
регистр DI. Пример применение команды
LEA следующий:
LEA BX, 10[DI]
Ассемблер
обратится по адресу содержимого регистра
DI, добавит смещение 10, и затем поместит
это значение (адрес) в регистр BX.
Аналогичной команды с непосредственным
операндом MOV для выполнения той же
функции не существует.
Механизм
адресации микропроцессора 8086 требует
определения сегмента и смещения каждой
переменной. Такое действие можно
выполнить с помощью команд загрузки
дальнего указателя LDS и LES (табл. 13). По
команде LDS(LES)
в сегментный регистр DS (ES)
и в указанный регистр общего назначения
загружается дальний указатель, значение
которого находится в указанной области
памяти. Флаги команда не изменяет.
Табл.
13. Команды загрузки дальнего указателя.
Код |
Инструкция |
Описание |
C5 /r |
LDS r16, m16:16 |
Загрузка дальнего |
C4 /r |
LES r16, m16:16 |
Загрузка дальнего |
Например,
команда:
LDS SI, A
загружает регистровую
пару DS:SI значениями сегмента и смещения,
содержащимися в переменной A: в регистр
SI помещается смещение, расположенное
по адресу A, а в регистр DS – значение
сегмента, расположенное по адресу A+2.
И
последняя инструкция, используемая в
операциях модификации адресов, XLAT (табл.
14). Инструкция перекодирует по таблице
значение регистра AL. Команда помещает
в регистр AL содержимое байта памяти по
адресу, равному сумме содержимого
регистров BX и AL. Флаги команда не меняет.
Команда используется при перекодировке
символов.
Табл. 14. Команда XLAT.
Код |
Инструкция |
Описание |
D7 |
XLAT |
AL:= [BX+AL] |
Предположим,
имеется таблица кодировки символов
размером до 256 байтов, и i-й элемент
рассматривается как новый код символа
с кодом i. Если начальный адрес таблицы
поместить в регистр BX, а исходный код
перекодируемого символа в регистр AL,
то команда XLAT поместит в регистр AL новый
код символа из i-ого элемента таблицы.
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]
- #
- #
- #
- #
- #
- #
- #
- #
- #
- #
- #