Файл: О.А.Калашников. Ассемблер Это Просто. Учимся программировать.pdf

ВУЗ: Не указан

Категория: Книга

Дисциплина: Программирование

Добавлен: 16.02.2019

Просмотров: 29236

Скачиваний: 1689

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
background image

 

Часть III. Файловая оболочка, вирус, резидент 

194 

Необходимая информация заносится в регистр 

al

 перед вызовом подпрограммы. 

Mul  bl

  умножает  содержимое 

bl

  на 

al

, результат помещается в 

ax

, который впо-

следствии переносим в 

cx

. Сохраним все эти данные в переменных для восстанов-

ления.  Копировать  будем  целыми  рядами,  т. е.  от  начала  левого  угла  экрана  до 
конца (

dl

  от  0  до  79;  для  этого  мы  и  обнуляем 

dl

  на  всякий  случай).  Копируется 

столько рядов, сколько будет занимать выводимое окошко.  

Теперь все готово. Переносим данные в область второй видеостраницы (первую 

занимает наша оболочка, вторую — пользовательский экран) (листинг 18.10). 

Листинг 18.10. Копируем видеостраницу 

... 
mov si,di        ;ds:si — откуда копируем 
xor di,di        ;es:si — куда копируем 
 
;Сохраним полученные значения для восстановления 
mov Num_copysi,si 
mov Num_copydi,di 
mov Num_copycx,cx 
 
push 0B800h      ;Настроим сегментные регистры 
pop ds 
push 0BA00h 
pop es 
rep movsb        ;Копируем... 
... 

Восстановление  происходит  достаточно  просто  и  по  тому  же  принципу,  что  

и сохранение экрана. (См. описание процедуры 

Restore_scr

.)  

18.3.5. Оператор scas 

Рассмотрим еще один оператор, позволяющий работать со строками (массивами 

данных) (табл. 18.1). 

Таблица 18.1. Оператор 

scas

 

Команда  Перевод 

Назначение 

Процессор 

scas 

Scan string 

— сканирование строки 

Поиск символа в массиве  8086 

 
Данный  оператор  имеет  две  разновидности,  подобно 

movs

  и 

stos

,  а  именно: 

scasb

  и 

scasw

.  Оператор 

scasb

  служит  для  поиска  первого  попавшегося  байта,  

а 

scasw

 —  первого  попавшегося  слова  (двух  байтов).  При  этом 

es:di

  должен  со-

держать адрес строки (листинг 18.11). 


background image

Глава 18. Высокоуровневая оптимизация программ 

195 

Листинг 18.11. Пример использования оператора scas 

... 
;es:di — адрес строки 

(1)    mov di,offset String 

;cx — максимальное количество сканируемых байтов/слов 

(2)    mov cx,offset String_len 

;Символ для поиска (9) 

(3)    mov al,9 

;Ищем первый байт, который находится в al 

(4)    repne scasb 

... 

 

(5) String db 1,2,3,4,5,6,7,8,9,10,11,12 

(6) String_len equ $-String 

... 

В 

cx

  заносим  длину  (количество  символов/байтов)  строки 

String

,  т. е.  12.  

В 

al

 — символ, который нам нужно найти. После выполнения строки (4) 

di

 будет 

указывать на адрес байта, следующего после найденного символа (т. е. на смеще-
ние числа 10). Вроде, все понятно, но возникают четыре вопроса: 
1.  Что такое 

repne

? Мы до сих пор работали только с 

rep

Итак,  оператор 

repne

  (о  англ.  repeat  if  not  equal —  повторять,  если  не  равно) 

сканирует строку (массив) до тех пор, пока символ, расположенный в 

al

/

ax

, не 

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

repe

  (от  англ.  repeat  if 

equal — повторять, если равно). 

2.  Существует ли префикс 

repe

Безусловно,  существует  и  выполняет  действия,  противоположные  действиям 
оператора 

repne

 (листинг 18.12). 

Листинг 18.12. Пример использования оператора repne 

... 
;es:di — адрес строки 
(1)    mov di,offset String 
;cx — максимальное количество сканируемых байтов/слов 
(2)    mov cx,offset String_len 
;Символ для поиска 
(3)    mov al,1 
;Ищем первый байт, отличный от того, который находится в al 
(4)  repe scasb 


background image

 

Часть III. Файловая оболочка, вирус, резидент 

196 

... 
 
(5) String db 1,1,1,1,1,6,1,1,1,1,1,1 
(6) String_len equ $-String 
... 

В данном случае после выполнения строки (4) регистр 

di

 будет указывать на 

адрес следующего за цифрой 6 байта (т. е. 1), а флаг нуля будет установлен. Мы 
можем проверить, найден ли байт, отличный от загруженного в 

al

, или нет, пу-

тем выполнения команды 

je Label

 (переход на метку 

Label

, если байт найден). 

Данный  префикс  (

repe

)  обычно  используется  для  поиска  первого  символа,  не 

равного тому, который содержится в 

al

/

ax

3.  Что произойдет, если в листинге 18.11 мы загрузим в 

al

 число 13? 

В таком случае, если после числа 12 в массиве 

String

 не находится байт с чис-

лом 13, будет сброшен флаг нуля, а 

di

 выйдет за пределы массива 

String

4.  Что произойдет, если в листинге 18.11 мы в 

cx

 загрузим, например, число 7? 

Это будет означать, что 

scasb

 выполнится 7 раз, при этом не дойдет до числа 9 

(которое загружено в 

al

). Следовательно, 

di

 будет указывать на число 8 в мас-

сиве 

String

, а флаг нуля будет сброшен. Мы можем элементарно проверить это, 

выполнив  команду 

jnz  Label

  (перейдем,  если  флаг  нуля  сброшен —  значит, 

символ не найден). 

18.3.6. Подсчет длины нефиксированной строки 

Мы рассмотрели оператор 

scas

, чтобы теперь использовать его в нашей оболоч-

ке для подсчета длины строки (

Count_strmid

, display.asm). Каким образом? 

Признаком окончания строки у нас является ASCII-символ 0. Считать же коли-

чество  символов  в  строке  необходимо  для  того,  чтобы  вывести  ее  в  центре  ряда  
на экране монитора. В дальнейшем мы будем постоянно использовать данную про-
цедуру, поэтому давайте сейчас ее подробнее рассмотрим (листинг 18.13). 

Листинг 18.13. Подсчет длины строки 

... 
(1)    push cs          ;es=cs 
(2)    pop es 
(3)    mov di,si        ;di=si 
(4)    xor al,al        ;al=0 
(5)    mov cx,0FFFFh    ;Сколько символов перебирать (возьмем максимум) 
(6)    repne scasb      ;Ищем 0 в массиве данных... 
 
;0 найден! di указывает на следующий символ за найденным нулем 
;si=начало строки 
;di=конец строки+1 
 
(7)    sub di,si        ;di=di-si-1 = длина строки 


background image

Глава 18. Высокоуровневая оптимизация программ 

197 

(8)    dec di 
(9)    shr di,1         ;Делим длину на 2 
(10)   mov ax,40        ;Делим количество символов в строке на 2 = 40 
(11)   sub ax,di        ;ax=40-половина длины строки = нужная колонка 
(12)   mov dl,al        ;dl=колонка, с которой следует выводить строку. 
... 

Перед вызовом этой процедуры мы в 

si

 загружаем адрес строки, в которой сле-

дует посчитать количество символов. Так как оператор 

scas

 работает с парой реги-

стров 

es:di

, то нам нужно сперва в 

di

  загрузить  содержимое  регистра 

si

  (строка 

(3)). Далее аннулируем 

al

 (4), заносим в 

cx

 максимальное число. Длину строки мы 

пока  не  знаем,  поэтому  предположим,  что  строка  имеет  максимально  возможную 
длину,  которую  может  содержать  регистр 

cx

.  Начался  поиск  (6)...  Итак,  нашли 

символ 

'0'

 (7). Мы его просто обязаны найти.  

Теперь  мы  имеем  следующее: 

si

  содержит  начальное  смещение  строки, 

di

 — 

конец строки + 1. Чтобы получить длину, нужно из 

di

  вычесть 

si

  и  еще  единицу 

((7), (8)): 

di

 = 

di

 – 

si

 – 1 = длина строки. 

Обратите внимание, как это выглядит в отладчике (рис. 18.5). 

 

Рис. 18.5. Вычислим длину строки и разделим эту длину на 2. di = длина строки / 2 

Затем  разделим  полученную  длину  на  два  (9).  Так как  на  экране  в  одном  ряду  

80 символов (режим 3), то 80 делим на два, чтобы получить середину ряда. Из се-
редины ряда вычитаем половину длины строки. Полные формулы: 

di

 = длина строки, 

dl

 = (80/2) – (

di

/2). 

Теперь 

dl

 содержит колонку, с которой следует начинать выводить строку. Это 

и будет центр ряда.  


background image

 

Часть III. Файловая оболочка, вирус, резидент 

198 

18.3.7. Вывод строки на экран  
путем прямого отображения в видеобуфер 

Пример вывода строки на экран показан в листинге 18.14. 

Листинг 18.14. Вывод строки (Print_string, display.asm) 

... 
Print_string proc 
(1)    call Get_linear         ;Получаем линейный адрес строки 
... 
 
Next_symstr: 
(2)    lodsb                   ;Получаем очередной символ строки 
(3)    or al,al                ;Это 0 (конец строки?) 
(4)    jz Stop_outstr          ;Да — выходим... 
;Иначе заносим в видеобуфер атрибут (ah) и символ (al) 
(5)    stows 
(6)    jmp short Next_Symstr   ;Следующий символ... 
 
(7) Stop_outstr: 
(8)    ret 
Print_string endp 
... 

Перед вызовом данной процедуры мы должны загрузить в пару регистров 

ds:si

 

адрес  строки  для  вывода,  в 

dx

 —  координаты  для  вывода  (

dh

 —  столбец, 

dl

 — 

строка (ряд)), а в 

ah

 — атрибуты выводимой строки. 

В самом начале мы вызываем процедуру перевода 

dx

 в линейный адрес. Для че-

го, ведь это замедляет работу программы в целом?  

 

Во-первых,  это  делается  для  удобства  написания  программы.  Как  вы  думаете, 
было бы удобно постоянно вычислять самим линейный адрес перед тем, как вы-
вести  строку,  или  отдать  эту  работу  отдельной  готовой  подпрограмме?  Безус-
ловно, гораздо проще, если необходимые вычисления произведет компьютер. 

 

Во-вторых,  что  было  бы  удобней  при  написании  кода:  указать  просто  стро-
ку/столбец или одно число — линейный адрес, который нужно вычислять про-
граммисту? Конечно же, первый вариант предпочтительней. 

 

В-третьих,  скорость  работы  программы  не  настолько  уж  падает.  Даже  на  ком-
пьютерах моделей 8086/8088 это абсолютно незаметно. Так что беспокоиться по 
поводу скорости нет необходимости. 
Строка, передаваемая подпрограмме, должна заканчиваться символом 0. Конеч-

но,  вы  можете  придумать  другое  ограничение  строки  (например,  как  у  функции 

09h

 — 

'$'

). Но вряд ли это будет удобно, т. к. нередко приходится выводить этот 

символ на экран, а вот вывод числа 0 вообще не имеет смысла.