22. Строковые команды - Это последняя большая группа команд

Лекции по предмету «Информатика»
Информация о работе
  • Тема: 22. Строковые команды - Это последняя большая группа команд
  • Количество скачиваний: 6
  • Тип: Лекции
  • Предмет: Информатика
  • Количество страниц: 11
  • Язык работы: Русский язык
  • Дата загрузки: 2014-11-09 22:13:45
  • Размер файла: 26.86 кб
Помогла работа? Поделись ссылкой
Информация о документе

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

Если Вы являетесь автором текста представленного на данной странице и не хотите чтобы он был размешён на нашем сайте напишите об этом перейдя по ссылке: «Правообладателям»

Можно ли скачать документ с работой

Да, скачать документ можно бесплатно, без регистрации перейдя по ссылке:

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
...
Команда 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
(movsb, movsw) (DI)  (SI)
модифицировать 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
(movsb, movsw, movsd) (EDI)  (ESI)
модифицировать 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), и в зависимости от этого генерирует команду movsb или movsw (или movsd). Проведем эксперимент: заменим в нашей программе movsb на movs mem2, mem1. Трансляция закончится неудачей:
Cant 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. Использование флага направления.
На первый взгляд флаг направления не нужен. В программах обработки массивов, которые мы писали до сих пор, мы всегда перемещали указатели в сторону возрастания адресов. Но вот пример, где такой подход не приведет к успеху.
Пример. Дана область памяти 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. Команды пересылки.
Помимо movs для пересылки цепочек используются еще две команды. С их помощью очередной элемент цепочки загружается в аккумулятор (AL — для байтов, AX — для слов, EAX — для двойных слов) или читается из аккумулятора. Это дает возможность преобразования элемента, пока он находится в аккумуляторе.
Загрузить элемент строки lods src
(lodsb, lodsw) ac  (SI)
модифицировать SI
(LOaD String) флаги не изменяются

Заполнить элемент строки stos dst
(stosb, stosw) (DI)  ac
модифицировать 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 (хотя сейчас это имеет лишь исторический интерес). Система команд у процессоров одна и та же. Одинаков формат регистра 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! Дело в том, что в очереди теперь не команды программы, а команды отладчика.
Это наводит на мысль, что можно создавать программы, защищенные от трассировки отладчиком.
Пример. Фроловы БСП т.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
(cmpsb, cmpsw) (SI) – (DI)
модифицировать SI и DI
(CoMPare Strings) флаги сост. изменяются

Сканировать элемент строки scas dst
(scasb, scasw) ac – (DI)
модифицировать 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] ; меняем знак у отрицательного
; с учетом того, что указатель
; уже перемещен на следующий
; элемент
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
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
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
В отладчике можно убедиться, что заполнение массива происходит правильно. 32-разрядный адрес преобразуется в 16-разрядный посредством отбрасывания старших разрядов.
22.7. Команды загрузки адресов
Для целей дальнейшего изложения здесь уместно ввести команды lds и les, которые к строковым командам не имеют прямого отношения.
Ранее была введена команда lea — загрузить эффективный (исполнительный) адрес. Вот еще две команды.
Загрузить указатель в сегменте данных lds reg,src reg  src
DS  src + 2
(Load pointer using DS) флаги не изменяются

Загрузить указатель в дополнительном сегменте данных les reg,src reg  src
ES  src + 2
(Load pointer using ES) флаги не изменяются
Источником src для этих команд служит операнд — двойное слово. Младшее слово загружается в регистр-указатель (как правило, SI или DI), а старшее — в сегментный регистр.