
- •16. Строковые команды
- •16.1. Команда пересылки строк.
- •Xor ax, ax
- •Inc_bxDb43h; код командыincbx,
- •Xorbx,bx; вbx- длина очереди
- •Xorax,ax; Счетчик несовпадающих элементов
- •Incax; Нашли совпадение — увеличить счетчик
- •Int 21h
- •Incdi; Вернуть указатель на найденный символ
- •Int 21h
- •16.7. Команды загрузки адресов
16. Строковые команды
Это последняя большая группа команд, которую нам предстоит рассмотреть. Эти команды позволяют с помощью одной-двух инструкций обработать целую строку. Сразу отметим, что принципиально новых возможностей (как, например, битовые команды) строковые команды не дают. Для написания программ, которые нам предстоит рассмотреть, можно было обойтись и уже изученными средствами. Но применение строковых команд увеличивает качество программ: уменьшает размер кода и резко увеличивает быстродействие. Кроме того, компиляторы языков высокого уровня, как правило, не генерируют строковые команды. Их использование возможно только средствами языка Ассемблера. Особенно эти команды полезны для компьютерной графики.
16.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
...
Команда movsb заменяет четыре команды первой программы. Что еще нового появилось в программе?
Во-первых, команда загрузки ES— регистра дополнительного сегмента данных (до сих пор мы его практически не использовали). В строковых командах приемник всегда находится в дополнительном сегменте.
Во-вторых, команда cld. Эта команда относится к классу команд изменения флагов. В регистреFlagsесть три управляющих флага. Один из нихDF(directionflag— флаг направления). Этот флаг устанавливается или сбрасывается специальными командами.
Сбросить DF |
cld |
DF 0 |
(CLear DF — очистка DF) |
|
Установить DF |
std |
DF 1 |
(SeT DF) |
|
Команда movsможет пересылать слова или байты. размеру операнда соответствует мнемоника:movsb— для байтов,movsw— для слов (начиная с 386-го процессора, появилась командаmovsd— для двойных слов). РегистрыSIиDIдолжны быть заранее настроены на нужные адреса (например так, как это сделано в нашей программе). Дадим формальное описание команды (для процессора 8086)
Переслать строку |
movs dst,src (movsb, movsw) |
(DI) (SI) модифицировать SIиDI |
(MOVeString) |
флаги не изменяются |
Расшифруем, что означает модификация содержимого SIиDIпосле выполнения операции копирования. Это перемещение указателейSIиDIна следующие элементы цепочек. Выполняется модификация в соответствии с таблицей.
|
DF= 0 (автоинкремент) |
DF= 1 (автодекремент) |
цепочка байтов |
sisi+1,didi+1 |
si si–1, di di–1 |
цепочка слов |
sisi+2,didi+2 |
si si–2, di di–2 |
Для процессора 80386 описание команды выглядит иначе.
Переслать строку |
movs dst,src (movsb, movsw, movsd) |
(EDI)(ESI) модифицировать ESIиEDI |
(MOVeString) |
флаги не изменяются |
|
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), и в зависимости от этого генерирует командуmovsbилиmovsw (илиmovsd). Проведем эксперимент: заменим в нашей программеmovsbнаmovs mem2, mem1. Трансляция закончится неудачей:
Can'toverrideESsegment(нельзя переопределять сегментES).
Точнее, нельзя указывать сегментный регистр для приемника, отличный от ES. Дело в том, что Ассемблеру мало того, что мы загрузили сегментные регистрыDSиES. Ему еще нужно директивное указание с какими логическими сегментами связаныDSиES. Для этого предназначена директиваASSUME(предполагать):
ASSUME es:@data
Теперь ESассоциирован сDGROUPи Ассемблер убежден, что операнд-приемник в командеmovsрасположен правильно — в дополнительном сегменте. (Возникает вопрос: а почему мы не включили аналогичные директивы для других сегментных регистров? За нас это сделала директива определения модели памяти .MODELsmallи упрощенные сегментные директивы).
Есть еще одно решение для преодоления ошибки трансляции — явно указать в команде префикс замены сегмента: movs es:mem2,mem1. Тогда можно обойтись без директивыASSUME.
Но лучше использовать строковые команды с явным указанием размера операндов: movsb, movsw, movsd.
16.2. Префикс повторения.
Программу можно еще улучшить. Для повышения эффективности использования строковых команд введен префикс повторения rep.
Повторять примитив |
rep примитив |
CXCX–1 покаCX0 |
(REPetition— повторение) |
|
Проведем последнее изменение нашей программы. Вместо команд
L: movsb
loop L
поместим одну команду
rep movsb
Время выполнения резко сокращается. Приведем расчеты для процессора 8086. В первом варианте тратится 18 + 17 = 35 на итерацию. Во втором: 9 + 17 тактов для первой итерации и 17 тактов на остальные.
Если CX= 0, то строковая команда с префиксом повторения не выполняется ни разу. (Здесь отличие от командыloop. Если перед выполнением этой командыCX= 0, то цикл выполняется 0FFFFhраз).
Выполнение команды с префиксом повторения имеет свои особенности при работе с TurboDebugger. Если вы нажимаете клавишуF8 (Step), тоrep movsbбудет выполнена как одна команда, а если нажимаетеF7, то командаmovsbбудет последовательно выполненаCXраз. Попробуйте!
16.3. Использование флага направления.
На первый взгляд флаг направления не нужен. В программах обработки массивов, которые мы писали до сих пор, мы всегда перемещали указатели в сторону возрастания адресов. Но вот пример, где такой подход не приведет к успеху.
Пример. Дана область памяти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. Напишем подпрограмму и макрос для ее вызова.
copstrPROC
cld; прямое направление
cmpsi,di; сравнение адресов цепочек
jer; Если совпадают — наr
jamove; Источник выше
std; Обратное направление
addsi,cx;
decsi;SIуказывает на последний элемент
adddi,cx
decdi;DIуказывает на последний элемент
move:rep movsb
r: ret
copstrENDP
Заметим, что сравнение 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.
16.4. Команды пересылки.
Помимо movsдля пересылки цепочек используются еще две команды. С их помощью очередной элемент цепочки загружается в аккумулятор (AL— для байтов,AX— для слов,EAX— для двойных слов) или читается из аккумулятора. Это дает возможность преобразования элемента, пока он находится в аккумуляторе.
Загрузить элемент строки |
lods src (lodsb, lodsw) |
ac(SI) модифицировать SI |
(LOaDString) |
флаги не изменяются |
Заполнить элемент строки |
stos dst (stosb, stosw) |
(DI)ac модифицировать DI |
(STOreString) |
флаги не изменяются |
По аналогии с командой 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эквивалентна по действию двум командам:lodssrcиstosdst. Но зато между этими командами можно поместить команды изменения содержимого аккумулятора.
Перед этими командами можно использовать префикс повторения, хотя для команды lodsон бесполезен. А вот с помощью командыrep stosудобно заполнять область памяти.
Пример. Обнулить двести байт, начиная с адресаmem.
EVENDATA ; директива выравнивания по четному адресу
mem DB 200 DUP(?)
...
mov ax, @data
mov es, ax
mov di, OFFSET mem
mov cx, 100