Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Assembler / P22

.pdf
Скачиваний:
53
Добавлен:
02.06.2015
Размер:
401.98 Кб
Скачать

1

22. Строковые команды

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

22.1. Команда пересылки строк.

Детально рассмотрим команду пересылки (копирования) строк. На ее примере мы легко поймем остальные строковые команды.

Пример. Скопируем содержимое области памяти mem1 в область памяти mem2 (области не перекрываются).

Сначала напишем программу с использованием уже известных нам средств.

.DATA

mem1 DB 4,5,6,1,2,3 mem1len = $ - mem1 mem2 DB memlen DUP(?)

.CODE start:mov ax, @data

mov ds,ax

mov si, OFFSET mem1 mov di, OFFSET mem2 mov cx, mem1len

L:mov al,[si] mov [di],al inc si

inc di loop L

...

Итак, мы успешно справились с задачей, используя изученные ранее средства. А теперь изменим программу, воспользовавшись строковой командой movs (ее еще называют строковым примитивом, так как на ее основе можно по-

строить более сложную команду).

.CODE

start:mov ax, @data mov ds, ax

mov es, ax

mov si, OFFSET mem1 mov di, OFFSET mem2 mov cx, mem1len

cld ; Сбросить флаг направления

L:movsb loop L

...

2

Команда movsb заменяет четыре команды первой программы. Что еще нового появилось в программе?

Во-первых, команда загрузки ES — регистра дополнительного сегмента данных (до сих пор мы его практически не использовали). В строковых командах приемник всегда находится в дополнительном сегменте.

Во-вторых, команда cld. Эта команда относится к классу команд изменения флагов. В регистре Flags есть три управляющих флага. Один из них DF (direction flag — флаг направления). Этот флаг устанавливается или сбрасывается специальными командами.

Сбросить DF

cld

DF 0

(CLear DF — очистка DF)

 

 

 

 

 

Установить DF

std

DF 1

(SeT DF)

 

 

Команда movs может пересылать слова или байты. размеру операнда соответствует мнемоника: movsb — для байтов, movsw — для слов (начиная с 386го процессора, появилась команда movsd — для двойных слов). Регистры SI и DI должны быть заранее настроены на нужные адреса (например так, как это сделано в нашей программе). Дадим формальное описание команды (для процессора 8086)

Переслать строку

movs dst,src

(DI) (SI)

 

(movsb, movsw)

модифицировать SI и DI

(MOVe String)

 

флаги не изменяются

Расшифруем, что означает модификация содержимого SI и DI после выполнения операции копирования. Это перемещение указателей SI и DI на следующие элементы цепочек. Выполняется модификация в соответствии с таблицей.

 

 

DF = 0 (автоинкремент)

 

DF = 1 (автодекремент)

 

цепочка байтов

si si+1, di di+1

 

si si–1, di di–1

 

цепочка слов

si si+2, di di+2

 

si si–2, di di–2

 

Для процессора 80386 описание команды выглядит иначе.

 

Переслать строку

 

movs dst,src

(EDI) (ESI)

 

 

(movsb, movsw, movsd)

модифицировать ESI и EDI

 

(MOVe String)

 

флаги не изменяются

 

 

 

 

 

 

 

 

 

DF = 0 (автоинкремент)

 

DF = 1 (автодекремент)

 

цепочка байтов

esi esi+1, edi edi+1

 

esi esi–1, edi edi–1

 

цепочка слов

esi esi+2, edi edi+2

 

esi esi–2, edi edi–2

 

цепочка двойных слов

esi esi+4, edi edi+4

 

esi esi–4, edi edi–4

 

Итак, если DF сброшен, то цепочка проходится в направлении увеличения адреса, а если DF установлен — в направлении уменьшения адреса. Приращение указателя равно размеру элемента данных.

Осталось пояснить вариант movs dst,src. Ассемблер по описанию операндов в секции данных распознает какие цепочки пересылаются — байтовые или состоящие из слов (или из двойных слов для процессора 386), и в зависимо-

3

сти от этого генерирует команду movsb или movsw (или movsd). Проведем эксперимент: заменим в нашей программе movsb на movs mem2, mem1. Трансляция закончится неудачей:

Can't override ES segment (нельзя переопределять сегмент ES).

Точнее, нельзя указывать сегментный регистр для приемника, отличный от ES. Дело в том, что Ассемблеру мало того, что мы загрузили сегментные регистры DS и ES. Ему еще нужно директивное указание, с какими логическими сегментами связаны DS и ES. Для этого предназначена директива ASSUME (предполагать):

ASSUME es:@data

Теперь ES ассоциирован с DGROUP и Ассемблер убежден, что операндприемник в команде movs расположен правильно — в дополнительном сегменте. (Возникает вопрос: а почему мы не включили аналогичные директивы для других сегментных регистров? За нас это сделала директива определения модели памяти .MODEL small и упрощенные сегментные директивы.)

Есть еще одно решение для преодоления ошибки трансляции — явно указать в команде префикс замены сегмента: movs es:mem2,mem1. Тогда можно обойтись без директивы ASSUME.

Но лучше использовать строковые команды с явным указанием размера операндов: movsb, movsw, movsd.

22.2. Префикс повторения.

Программу можно еще улучшить. Для повышения эффективности использования строковых команд введен префикс повторения rep.

Повторять примитив

 

rep примитив

CX CX–1 пока CX 0

(REPetition — повторение)

 

 

Проведем последнее изменение нашей программы. Вместо команд

L:movsb loop L

поместим одну команду rep movsb

Время выполнения резко сокращается. Приведем расчеты для процессора 8086. В первом варианте тратится 18 + 17 = 35 на итерацию. Во втором: 9 + 17 тактов для первой итерации и 17 тактов на остальные.

Если CX = 0, то строковая команда с префиксом повторения не выполняется ни разу. (Здесь отличие от команды loop. Если перед выполнением этой команды CX = 0, то цикл выполняется 0FFFFh раз).

Выполнение команды с префиксом повторения имеет свои особенности при работе с Turbo Debugger. Если вы нажимаете клавишу F8 (Step), то rep movsb будет выполнена как одна команда, а если нажимаете F7, то команда movsb будет последовательно выполнена CX раз. Попробуйте!

22.3. Использование флага направления.

На первый взгляд флаг направления не нужен. В программах обработки массивов, которые мы писали до сих пор, мы всегда перемещали указатели в сторону возрастания адресов. Но вот пример, где такой подход не приведет к успеху.

4

Пример. Дана область памяти m DB 1,2,3,4,0,0,0,0.Скопировать подстроку из первых четырех элементов на две позиции вправо (т.е. в итоге нужно получить m DB 1,2,1,2,3,4,0,0)

Если мы начнем перемещать по элементу от начала к концу, то получим следующее

исходное состояние

1, 2, 3, 4, 0, 0, 0, 0

1-й шаг

1, 2, 1, 4, 0, 0, 0, 0

2-й шаг

1, 2, 1, 2, 0, 0, 0, 0

3-й шаг

1, 2, 1, 2, 1, 0, 0, 0

4-й шаг

1, 2, 1, 2, 1, 2, 0, 0

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

mov si, OFFSET m + 4 mov di, OFFSET m + 6 mov cx, 5

std

rep movsb

Пример. Напишем подпрограмму, которая производит перемещение данных, предварительно выясняя направление. Пусть ES = DS. Напишем подпрограмму и макрос для ее вызова.

copstr PROC

cld

; прямое направление

cmp

si,di

; сравнение адресов цепочек

je r

 

; Если совпадают — на r

ja move

; Источник выше

std

 

; Обратное направление

add

si,cx

;

dec

si

; SI указывает на последний элемент

add

di,cx

 

dec

di

; DI указывает на последний элемент

move:rep

movsb

 

r: ret

 

 

copstr ENDP

Заметим, что сравнение SI и DI беззнаковое (сравниваем адреса). Макрос для вызова copstr:

copy MACRO src, dst, len mov si, OFFSET src mov di, OFFSET dst mov cx, len

call copstr

ENDM

Для нашего первого примера, с которого мы начали изучать строковые команды, вызов макроса имеет вид copy mem1, mem2, mem1len.

22.4. Команды пересылки.

5

Помимо movs для пересылки цепочек используются еще две команды. С их помощью очередной элемент цепочки загружается в аккумулятор (AL — для байтов, AX — для слов, EAX — для двойных слов) или читается из аккумулятора. Это дает возможность преобразования элемента, пока он находится в аккумуляторе.

Загрузить элемент строки

lods

src

ac (SI)

 

(lodsb,

lodsw)

модифицировать SI

(LOaD String)

 

 

флаги не изменяются

 

 

 

 

Заполнить элемент строки

stos

dst

(DI) ac

 

(stosb,

stosw)

модифицировать DI

(STOre String)

 

 

флаги не изменяются

По аналогии с командой movs должно быть ясно: SI ассоциирован с DS, а DI с ES. В командах lodsb и stosb индексный регистр меняется на 1 (на +1, если DF = 0, и на –1, если DF = 1), а в командах lodsw и stosw — на 2. Соответственно, ac — это AL или AX. (Для процессора 386 добавляются команды lodsd и stosd).

Мы видим, что одна команда movs dst, src эквивалентна по действию двум командам: lods src и stos dst. Но зато между этими командами можно поместить команды изменения содержимого аккумулятора.

Перед этими командами можно использовать префикс повторения, хотя для команды lods он бесполезен. А вот с помощью команды rep stos удобно заполнять область памяти.

Пример. Обнулить двести байт, начиная с адреса mem. EVENDATA ; директива выравнивания по четному адресу mem DB 200 DUP(?)

...

mov ax, @data mov es, ax

mov di, OFFSET mem mov cx, 100

xor ax, ax rep stosw

Этот фрагмент выполняется вдвое быстрее, чем если бы мы повторили 200 раз команду stosb.

22.5. Неожиданное применение строковых команд — определение типа процессора.

Бывают случаи, когда программа должна определить тип процессора, на котором она работает (ну, например, программы диагностики оборудования). Тип процессора можно определить только по косвенным признакам. Серия программ, различающих процессоры (от 8086 до Pentium) приведена в книге Правиков "Ключевые дискеты". Эти программы, как правило, анализируют новые флаги, появляющиеся в регистре флагов с увеличением номера процессора. В Pentium воявилась команда CPUID, с возможностями которой мы ознакомимся позже, и необходимость в специальных программах отпала.

Рассмотрим задачу различения процессоров 8086 и 8088 (хотя сейчас это имеет лишь исторический интерес). Система команд у процессоров одна и та

6

же. Одинаков формат регистра Flags (на различиях в этом регистре основаны программы диагностики для старших моделей процессоров). Измерять быстродействие обращения к внешней шине (у 8086 она 16-разрядная, у 8088 — 8- разрядная) — дело безнадежное. Но можно воспользоваться косвенным признаком — длиной очереди команд. Очередь команд в шинном интерфейсе занимает у 8086 — 6 байтов, а у 8088 — 4 байта. Идея алгоритма: программа изменяет свой код одной командой rep stosb. При этом код команд, находящихся в

очереди, изменить невозможно.

...

.DATA

len DB ?, '$' ; строка для вывода длины конвейера inc_bx DB 43h ; код команды inc bx,

; получен с помощью debug

.CODE

 

 

 

 

 

start: mov ax, @data

 

 

 

 

mov ds,ax

 

 

 

 

 

xor bx,bx

; В BX - длина очереди

 

 

 

cld

 

 

 

 

 

mov di, OFFSET cs:_nop

 

 

 

 

mov ax, cs

 

 

 

 

 

mov es, ax ; Настроим ES на кодовый сегмент

 

 

mov al, inc_bx

 

 

 

 

mov cx,6 ; попытаемся изменить 6 байтов

 

 

cli ; запрет внешних прерываний

 

 

 

rep stosb

 

 

 

 

_nop: ; Здесь поместим 6 команд nop

 

 

REPT 6

 

 

 

 

 

nop

 

 

 

 

ENDM

 

 

 

 

 

 

sti ; разрешить прерывания

 

 

 

mov ax,6

 

 

 

 

 

sub ax, bx ; Получить длину очереди

 

 

add al, '0' ; Сформировать код цифры

 

 

mov len, al

 

 

 

 

 

message len

 

 

 

 

...

 

 

 

 

 

При запуске программы на 8088 на экран будет выведено число 4 — длина

очереди, т.к. команды после метки _nop приняли вид

 

 

nop

nop

nop

nop

inc bx

inc bx

очередь Команды, находящиеся в очереди, не претерпели изменений, а команды,

оставшиеся в ОЗУ, — изменились. Если выполнить эту программу на 8086 (и выше), то будет выведена длина очереди 6 (хотя на 80386 длина очереди составляет 15 байт).

Любопытно, что если запустить эту программу под управлением отладчика, то программа выведет на экран длину очереди 0! Дело в том, что в очереди теперь не команды программы, а команды отладчика.

Это наводит на мысль, что можно создавать программы, защищенные от трассировки отладчиком.

7

Пример. Фроловы БСП т.1 кн. 3

.DATA

s DB "Программа работает под управлением отладчика!",

CRLFT

...

cli

call Test sti

...

Test PROC near

mov byte ptr next, 90h ; код команды nop next: ret

message s ret

Test ENDP

22.6. Команды сравнения Эти две команды осуществляют сравнение элементов цепочек, не меняя

операндов. Удобство их применения — в автоматическом переходе к следующему элементу цепочки

Сравнить элементы строк

cmps src,dst

(SI) – (DI)

 

 

(cmpsb, cmpsw)

модифицировать SI и DI

 

(CoMPare Strings)

 

флаги сост. изменяются

 

 

 

 

 

 

Сканировать элемент строки

scas dst

 

ac – (DI)

 

 

(scasb, scasw)

 

модифицировать DI

 

(SCAn String)

 

 

флаги сост. изменяются

 

По результатам этих операций выставляются флаги, которые анализируются последующими командами. (Для процессора 386 добавляются команды cmpsd и scasd).

Пример. Заменить каждый элемент байтового массива его абсолютной величиной (в предположении, что в массиве нет элемента 80h).

.DATA

m DB 1,-2,3,2,1,-3 len_m = $ - m

.CODE

start:

mov ax, @data mov ds, ax mov es, ax cld

mov al,0

mov cx, len_m mov di, OFFSET m

p:scasb ; из 0 вычитаем элемент массива

jl n ; если результат отрицательный

; то исходный элемент положительный

neg byte ptr [di-1] ; меняем знак у отрицательного

;с учетом того, что указатель

;уже перемещен на следующий

8

; элемент

n:loop p

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

Повторять примитив пока

 

repe/repz примитив

CX CX-1

пока

равно (нуль)

 

 

(CX 0 и ZF = 1)

(REPetition if Equal/Zero)

 

 

 

 

 

 

 

Повторять примитив пока

 

repne/repnz примитив

CX CX-1

пока

не равно (не нуль)

 

 

(CX 0 и ZF = 0)

(REPetition if Not Equl or Zero)

 

 

 

Пример. Определение длины строки. string DB "abc", 0

mov ax, @data mov es, ax cld

mov al,0 mov cx, -1

mov di, OFFSET string repne scasb

not cx dec cx

Проследим выполнение этого фрагмента, начиная с команды repne scasb.

a

b

c

0

 

CX = FFFFh

CX = FFFEh

CX = FFFDh

CX = FFFCh

CX = FFFBh

DI в начале

 

 

 

DI в конце

На схеме показано начальное и конечное положение указателя DI (в конечном положении DI содержит адрес байта, следующего за нулевым). Показано также текущее значение счетчика CX. Команда not cx инвертирует биты CX. В результате CX = 4. Команда dec cx корректирует это значение. В результате в CX длина строки (без учета терминатора строки — нулевого байта).

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

.MODEL small

.STACK 100h

.DATA

s1 DB "abcdesss" len_s1 = $ - s1 s2 DB "abcdfsss"

.CODE

start:

mov ax,@data mov ds,ax

9

mov es,ax cld

mov cx, len_s1

xor ax,ax ; Счетчик несовпадающих элементов mov si, OFFSET s1

mov di, OFFSET s2

comp: repe cmpsb ; Повторять, пока совпадение je fin ; Исчерпали строки — закончить

inc ax ; Нашли совпадение — увеличить счетчик or cx,cx; Строки исчерпаны?

jne comp ; Нет — продолжим сравнение fin: mov ax, 4C00h

int 21h

END start

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

string db "asdzbxzar",0

...

mov ax, @data mov es, ax cld

mov al,0

mov di, OFFSET string mov cx, -1

repne scasb ; Найти конец строки dec di ; Переместить указатель на 0

std ; Поиск в направлении уменьшения адресов mov al, 'z'

mov cx, -1 repne scasb

inc di ; Вернуть указатель на найденный символ

; В DI — адрес символа

...

Эта программа некорректна. Она правильно работает, если в строке заведомо имеется символ 'z'.

Упражнение. Переработать этот фрагмент так, чтобы программа корректно работала и при отсутствии символа 'z'.

Пример. В строке превратить прописные буквы в строчные.

.MODEL small

.STACK 100h

.DATA string DB "aSd1-E" lenstr = $ - string

.CODE start:mov ax,@data

mov ds,ax mov es,ax

mov si, OFFSET string mov di,si

mov cx,lenstr

10

cld

L:lodsb

cmp al,'A' jnae m

cmp al,'Z' jnbe m

add al, 'a'-'A'

m:stosb loop L

mov ax,4C00h int 21h

END start

Часть цикла можно было переписать эффективнее: add al, 'a'-'A'

stosb loop L

jmp short fin

m:inc di loop L

fin: mov ax,4C00h

 

Тогда не происходит лишнего обращения к памяти, а только перемещается

указатель.

 

 

 

 

 

 

А теперь пример на новые возможности 386-го процессора.

 

Пример. Заполнить массив двойных слов числом 12345678h. Сразу посмот-

рим листинг.

 

 

 

 

 

1

0000

 

 

MODEL

small

2

 

 

 

.386

 

 

3

0000

 

 

STACK 100h

4

0000

 

 

.DATA

 

5

0000

0C*(00000000)

arr

DD

12 DUP(0)

6

0030

 

 

.CODE

 

7

0000

B8

0000s

start:mov ax,@data

8

0003

8E

D8

 

mov ds,ax

9

0005

8E

C0

 

mov es,ax

10

0007

66| B8 12345678

 

mov eax,12345678h

11

000D

66| B9 0000000C

 

mov ecx,12

12

0013

66| BF 00000000r

mov edi,OFFSET arr

13

0019

FC

 

 

cld

 

14

001A

F3> 66| AB

 

rep stosd

15

001D

B8

4C00

 

mov ax,4C00h

16

0020

CD 21

 

int 21h

17

 

 

 

 

END start

Отметим , что в листинге для команды rep stosd имеется два префикса: 66 — префикс размера операнда, и F3 — префикс повторения.

И еще замечание, не имеющее отношения к строковым операциям. Вместо команды mov ecx,12 можно написать mov cx,12, а вместо mov edi,OFFSET arr — mov di,OFFSET arr. Убедимся, например, во втором утверждении. Заменим команду mov edi,OFFSET arr двумя командами:

mov edi, 0FFFF0000h mov di,OFFSET arr

Соседние файлы в папке Assembler