Word ptr ассемблер что это

Группа директив для указания типа процессора

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

.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, DFDPDQ, 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

Оглавление


                         Выбор размера данных
-----------------------------------------------------------------

     В языке Ассемблера с помощью инструкции 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 =


Оглавление

.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

iTech Techno's user avatar

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

Fifoernik's user avatar

FifoernikFifoernik

9,7291 gold badge20 silver badges27 bronze badges

Содержание

Введение в Ассемблер. Работа с регистрами. Адресация и команды пересылки данных. Арифметические операции с целыми числами

Цели:

  1. закрепить знания о регистрах общего назначения 32-разрядных процессоров INTEL;

  2. научиться использовать косвенную адресацию для работы с оперативной памятью;

  3. научиться использовать команды умножения и деления целых чисел.

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

Адресация и выделение памяти

Для процессора вся память представляет собой последовательность однобайтовых ячеек, каждая из которых имеет свой адрес. Для того, чтобы оперировать большими числами, пары ячеек объединяют в слова, пары слов – в двойные слова, пары двойных слов – в учетверенные слова. Чаще всего в программах оперируют байтами, словами и двойными словами (в соответствии с одно-, двух- и четырехбайтовыми регистрами процессоров). Адресом слова и двойного слова является адрес их младшего байта.

На листинге 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 в
r/m8.

89 /r

MOV r/m16, r16

Пересылка из r16 в
r/m16.

8A /r

MOV r8, r/m8

Пересылка из r/m8 в
r8.

8B /r

MOV r16, r/m16

Пересылка из r/m16 в
r16.

8C /r

MOV r/m16, Sreg

Пересылка из Sreg в
r/m16.

8E /r

MOV Sreg, r/m16

Пересылка из r/m16 в
Sreg.

A0 ow

MOV AL, m8

Пересылка из m8 в AL.

A1 ow

MOV AX, m16

Пересылка из m16 в
AX.

A2 ow

MOV m8, AL

Пересылка из AL в m8.

A3 ow

MOV m16, AX

Пересылка из AX в
m16.

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 в
r/m16.

Командой
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

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
исполнительного адреса ячейки m.

Команда
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

Загрузка дальнего
указателя в DS:r16.

C4 /r

LES r16, m16:16

Загрузка дальнего
указателя в ES:r16.

Например,
команда:

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-ого элемента таблицы.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]

  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #

Понравилась статья? Поделить с друзьями:
  • Word psychology comes from
  • Word prove it test
  • Word proser что это
  • Word proper что это
  • Word pronunciations in english