Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

Ассемблер? Это просто! Учимся программировать Выпуск N 017 (Оболочка)


Служба Рассылок Subscribe.Ru проекта Citycat.Ru

Здравствуйте, уважаемые подписчики!


Выпуск N 017

Сегодня в рассылке:


Ваши письма

Да, дорогие мои! Не думал, что мои рассуждения по поводу Беларуси и России из прошлого выпуска затронут многих! Очень рад, что все же народ интересуется политикой!

Позвольте привести несколько писем:

__________

1. С интересом прочитал 17-й выпуск и решил написать письмо.

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

В этом плане радует то, что в последнее время на российских телеканалах, существенно уменьшилось количество обгаживаний Белоруссии.

__________

2. Пару слов о России и Беларуси.

Мне кажется, Вы кое-что упустили. Многие у нас думают (я из Беларуси), что у нас живётся лучше, чем в России, и я с этим согласен. Есть Россия, а есть Москва, и это две разные вещи. Мы видим, что происходит на окраинах России, и мы не хотим, чтобы это происходило у нас. Могу добавить, что страх белорусских матерей, которые боятся, что их сыновья первыми попадут в какую-нибудь новую заварушку, вроде Чечни, - ещё одна немаловажная причина усомниться в этом союзе. Вспомните историю: во всех войнах белорусы страдали больше всех. Хочу сказать, что на улицах Минска, слава Богу, пока не стреляют (в отличие от Москвы). Я не против союза, но он должен быть чисто экономического характера.

__________

3. С первой частью 17 номера полностью с тобой согласен. Нас разделили всех для того чтобы поставить на колени - и неплохо получается. Хотя мне по барабану какой ты национальности, но будь человеком и не ставь себя выше других.

__________

4. Я родился и вырос в Белоруссии, прожил там 17 лет. Родители еще живы и живут здесь, в Белоруссии. Потом Московский авиационный институт, а затем распределение в Киев, на Украину. Прожил там я с августа 1977 года до мая 1999 года. За это время повидал всякого. Особенно запомнился 1991 год, провозглашение "самостийности и соборности" Украины. Ликующий народ, который полагал, что теперь он сам будет есть "сало, цукор, олию и ковбасу", что заживет счастливо и богато. Газеты, пишущие об исключительности украинцев и "дикости" русских. Между прочим, в историческом музее Киева сегодня не узнаешь о том, что Киевская Русь - это единое государство и колыбель белорусов, русских, украинцев.

Киевская Русь - это Украина. Часть моих друзей, с которыми бок о бок прожил не один год, удивила меня своим русофобством. Произошедшие с иными метаморфозы не могли не удивлять.

Но ко всему я отнесся спокойно. Я сказал им всем, что хочу, чтобы их желания и мечты сбылись как можно скорее, но боюсь, что их ждут разочарования. Чудес не бывает. Я голосовал на референдуме против независимости не потому, что не хотел счастья этим людям, а потому что понимал, развал огромной державы ни к чему хорошему привести не может. Друзьям я открыто говорил о своей позиции, оставляя за ними право на собственное мнение и собственные ошибки. И что же? Теперь практически все они поняли, что ошиблись. Кстати, опросы общественного мнения на Украине показывают, что более 60% людей сожалеют о развале единой страны, а более половины, хотели бы жить вместе с Россией.

Шишки набили, увы. Правители Украины твердят о стабильности, практическом отсутствии инфляции, начале экономического подъема. Читаю новости Украинского финансового сервера ежедневно, да и в Киеве бываю часто, там осталась моя семья. На деле же - мертвая промышленность, долларовая инфляция (гривня стоит, как у мертвого шея, а цены в долларах ползут, как в Африке), массовый исход трудоспособного населения за пределы Украины. Так я оказался вновь в Белоруссии, а мои друзья теперь кто где: одни в Южной Корее, другие в Канаде, третьи в Израиле, четвертые в Германии. Но большинство уехали в Россию.

Грустно. Грустно от того, что все мы не можем понять, что наши пробемы порождены нами же самими, что за нас их никто не решит. Что захлопнув дверь перед соседом мы закрываем ее в первую очередь для себя, станем беднее во всех отношениях. А национальность здесь совершенно ни при чем.

Но я все же настроен оптимистически. Люди рано или поздно поймут и все станет на свои места. А если же реформы в России увенчаются успехом, и начнется реальный подъем экономики, то агитировать никого не придется. Все вернется на круги своя. Только вот прежнего братства уже не будет (была "империя"), будет обычный нормальный капитализм, где всем верховодит экономическая целесообразность.

Поживем - увидим.

__________

5. Я сам живу (и жил) Украине, в городе Львове. Ну, все, наверное, знают, про рагулизм, там обитающий. Если действительно года 2-3 назад состояние было более-менее нормально, платили и пенсии, и стипендии, и зарплаты так ща всем глубоко и далеко до ентонго всего!!! Средняя зарплата составляет 30-40 баксов, ни один завод не пашет, и вообще, сам президент, как бы так сказать, не очень хороший человек...

Зарплаты, если это так можно назвать, не платят. Например, моему знакомому (он уже 5 лет работает в больнице) выдали недавно за декабрь позапрошлого года. А в моем родном городе говорят, что во всем виноваты "москали" и страшно ненавидят всех, кто говорит по-русски, и некоторые жестоко избивают так званых "москалив"...

А мозги вообще промывают по-черному: все сводится к тому, что во всем виновата Россия. И даже в том, что Чернобыль рванул... Так что, правильно. Не надо поддаваться на провокации, надо хорошо о всем подумать и иметь свое мнение, а не идти на поводу у тех козлов, которые это все затеяли...

_________

В общем, мнения не сильно расходятся. Радует хоть то, что я не получил ни одного отклика против объединения. Спасибо всем, кто написал, хотя письма еще приходят...


Вы просите высылать рассылку вместе с файлом-приложением. Друзья мои! Я не знаю, позволяет ли Subscribe.ru это делать или нет. Я облазил весь сайт, но не нашел ничего подобного. Написал письмо с данной просьбой. Надеюсь, что скоро получу ответ.

_________

Newmail.ru. Ой, "глючит" последнее время newmail! Ой-ой, "глючит"!!! Бывает, рассылка готова, а закачать на сервер файл-приложение не могу - не грузится страница!

В ближайшее время есть планы перейти на нормальный платный сервер. Но это пока только в планах.

Настоящий выпуск делаю как прежде. Вы уж извините за такой "сервис". Постараюсь что-нибудь сделать скоро.


Учимся оптимизировать программы

Информация для тренировки.

Письмо от одного из наших экспертов, которого зовут Slava V.:

Hi Олег!
Собственно вот тут усмотрел у тебя в программе оболочки: "С моей точки зрения - это довольно-таки оптимальный алгоритм... Если кто сможет написать лучше - прочту с удовольствием и опубликую в рассылке."

Это твой вариант... (14 строк)

========= из файла display asm Procedure Get_linear ===========
push ax ;сохраним все используемые регистры
push bx
push dx
shl dl, 1 ;математика: умножаем DL на 2 (DL=DL*2)...
mov al, dh ;в AL - ряд,
mov bl, 160 ;который нужно умножить на 160
mul bl ;умножаем: AL(ряд)*160; результат --- в AX
mov di,ax ;результат умножения в DI
xor dh,dh ;аннулируем DH
add di,dx ;теперь в DI линейный адрес в видеобуфере...
pop dx ;восстанавливаем регистры...
pop bx
pop ax
ret

А вот мой вариант (12 строк):
================================
push ax
push dx
xor ax, ax
xchg dh, al ; Имеем в dx =dl, а в ax = dh
mov di, ax
shl ax, 6 ; dh*64
shl di, 4 ; dh*16
add di, ax
add di, dx
shl di, 1 ; *2
pop dx
pop ax
ret
==================

То что мой вариант короче на 2 строки - это мелочи. Не в этом соль. Он быстрее, так как я использую команду сдвига (shl), а не умножения (mul). И регистров использует меньше (только ax).

Тут еще вот какой момент:

Ты используешь 160, т.е. уже умножил ширину экрана на 2. Но ведь ты потом все равно умножаешь на 2 dl. И почему только сдвиги:
Линейный адрес = (COL*80+RAW)*2
Отсюда:
Линейный адрес = (COL*64+COL*16+RAW)*2
Или:
Линейный адрес = ((COL shl 6) + (COL shl 4) + RAW) shl 1

Good luck. Slava V.

___________

Все верно! Алгоритм, который привел Slava V. работает гораздо быстрее. Если сможете разобраться - молодцы! А нам осталось поблагодарить Slav'у!

Вообще запомните: команды DIV (деление) и MUL (умножение) работают (выполняются) гораздо медленнее, чем операции сдвига.

_____________

Письмо от подписчика Sokol:

Я являюсь подписчиком твоей рассылки почти с самого начала и отслеживаю всякого рода изменения (мы даже немного переписывались на моих первых шагах). И когда только подписался, я был просто полным чайником в ассемблере. (А я проявлял дикий интерес к этому языку очень давно, но не мог найти ничего стоящего в интернет'е). Но с помощью твоей рассылки я стал разбираться в asm. Конечно не без помощи книг. (Кстати, для профессионалов советую Зубкова С.В. "Ассемблер для DOS, Windows и Unix", Москва 2000). Огромное тебе спасибо и не в первый раз.

Спасибо тебе!

_____________

Ссылка на ресурс от подписчика Andrey:

P.S. Еще одна неплохая библиотека компьютерной документации:
http://www.infocity.kiev.ua. Чем она удобна - все статьи готовы для скачивания и чтения в off-line.

Спасибо!

_____________

Приходит много писем с вопросом о том, будем ли мы программировать под Windows. Еще раз сообщаю: мы будем писать программы под Win32 после того, как пройдем DOS.


Ошибка

В прошлом выпуске я допустил одну ошибку. Вероятно, все уже это заметили. Что можно сказать: плохо это или хорошо? Я думаю, что мои ошибки идут вам на пользу. Почему? Вот вы читаете рассылку. И вдруг - опа! Что-то не стыкуется. Что вы начинаете делать? Правильно! Начинаете думать: почему так?

Покопавшись в рассылке, изучив файл-приложение вы вдруг находите ошибку! Разве это не счастье? Разве вы не радуетесь, как ребенок. А если бы я не допустил эту ошибку? Вы такое бы испытали?

Тем не менее, друзья мои, я постараюсь быть повнимательней...


Оболочка Super Shell

В прошлый раз мы с вами болтали много о жизни. Поэтому сегодня придется хорошо поработать.

Итак, возьмите, пожалуйста, файл-приложение здесь: http://www.oleg77.newmail.ru/Assembler/Programs/Lessons/Sshell18.rar

Прежде всего запустите полученный файл (SHELL18.COM), и посмотрите что он делает. Понажимайте на Ctrl+F5, ESC. Теперь будет разбирать...

Сразу же открывайте MAIN.ASM. Смотрите, как мы теперь вызываем процедуру Draw_frame. Это называется передача данных процедуре через стек. При программировании под Windows вызов всех процедур производится таким способом. В Windows нет понятия "прерывание". Есть понятие "системная функция". Но это так, к слову. Самое главное для вас - понять принцип передачи данных в стеке. Хотя, на самом деле все очень просто.

Принцип таков: занося в стек, например, 20 байт, процедура, получившая управление с параметрами в стеке, должна самостоятельно их достать, чтобы выровнять стек. Как?

Существует оператор RET N, где N - это количество освобождаемых байт из стека. В принципе, оператор RET N аналогичен RET.

Давайте рассмотрим все на простом примере:

_________

(1) push 123h
(2) call Our_pr
(3) pop ax

...

(4) Our_pr proc
...
(5) ret
(6) Our_pr endp

_________

Здесь мы вызываем процедуру Our_pr (2), предварительно занеся в стек какой-то параметр для данной процедуры (1). Что такое параметр? Например, для вывода строки на экран с помощью функции 09 мы должны передать данной функции параметр - адрес строки для вывода.

Процедура Our_pr отработает и выйдет, при этом в стеке останется этот параметр (123h). Нам нужно вытащить его со стека, т.к. стек у нас остается невыровненным. Мы это и делаем в строке (3).

Вроде все понятно. Но что делать, если мы передаем не один, а 20 параметров. Каждый раз при вызове процедуры доставать их со стека? Громоздко, неудобно и медленно. Более того, нам нужно использовать какой-нибудь регистр, куда можно будет доставать параметры. А регистры нужно беречь! Их не так уж и много, чтобы ими "разбазариваться". Можно воспользоваться оператором RET N. Вот как будет выглядеть приведенный выше пример с использованием данного оператора:

_________

(1) push 123h
(2) call Our_pr
...

(3) Our_pr proc
...
(4) ret 2
(5) Our_pr endp

_________

Оператор RET 2 вытащит со стека прежде всего адрес для возврата, а затем увеличит SP на 2 (т.е. как бы "искусственно" достанет со стека данные, причем не используя никаких регистров).

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

Конечно, если процедуре нужно передать два-три параметра, то можно (и желательно) передавать их и в регистрах. А если 10, как у нас при вызове Draw_frame? В прошлый раз мы заводили специальные три переменные. Но теперь мы существенно усовершенствовали нашу процедуру. Мы уже передаем 10 параметров. Следовательно, будем передавать их в стеке:

__________

push 23 ;высота
push 78 ;ширина
push 1F00h ;цвет
push offset Mess_head ;надпись вверху
push 1E00h ;ее цвет
push offset Mess_down ;надпись внизу
push 1D00h ;ее цвет
push 0 ;сообщение внутри рамки
push 0 ;его цвет
push 0 ;копировать ли экран?
call Draw_frame ;Рисуем рамку

__________

Сразу отмечу, что команды вида PUSH 23, PUSH 0 и т.п. толкают в стек два байта, а не один.

Теперь считаем количество "запиханных" в стек байт:

push 23 - 2 байта
push 78 - 2 байта
push 1F00h - 2 байта
push offset Mess_head - 2 байта

и т.д.

Итого: 20 байт. Следовательно, процедура Draw_frame должна доставать со стека столько же байт. Т.о. выход из этой процедуры будет таким: RET 20. Следует также иметь в виду, что при вызове данной процедуры нужно будет всегда заносить в стек 20 байт (10 слов), даже если эти данные ей не нужны. Иначе мы оставляем стек невыровненным!

Возникает вопрос: как нам получить доступ в процедуре к занесенным в стек параметрам? Очень просто!

Для этой цели принято использовать регистр BP, который мы мало где использовали в наших предыдущих примерах. Только еще раз напоминаю: стек растет снизу вверх. Первый параметр, занесенный в стек - последний для нашей процедуры. И наоборот.

Давайте сперва возьмем самый простой пример:

__________

...
(1) push offset Message1
(2) push offset Message2
(3) call Print_string
...
(4) Print_string proc
(5) mov bp,sp
(6) add bp,2
(7) mov ah,9
(8) mov dx,[BP]
(9) int 21h
(10) mov dx,[BP+2]
(11) int 21h
(12) ret 4
(13) Print_string endp
...
(14) Message1 db 'Привет!$'
(15) Message2 db 'Это я!$'

__________

Здесь мы вывели две строки на экран, используя дважды функцию 09 прерывания 21h. Это, конечно, ужасно, но для теоретического исследования пойдет.

Как вы думаете, что произойдет?

Если вы хорошенько подумали, то сообразили, что сперва выведется строка "Это я!", а затем - "Привет". Если вы не поняли, то повторю: стек растет снизу вверх. Следовательно, первый параметр, занесенный в стек будет последним! Строка (8) получает адрес Message2, а (10) - Message1. Попрошу не путать!

Обратите внимание, что в самом начале процедуры (строки (5) - (6)) мы заносим в BP текущее состояние стека, а затем увеличиваем BP на 2. Это нужно для того, чтобы "перепрыгнуть" адрес возврата, который занесла инструкция в строке (3). Так просто удобней немного будет считать.

И еще не забывайте выходить из процедуры соответствующим образом, как в строке (12). Мы занесли 4 байта, нам и выйти нужно, используя RET 4.

"У-у-у, Калашников, ну ты даешь! Это ж как неудобно-то! Лучше я заведу 10, 50, 100 переменных и буду работать с ними, чем через стек. Бог сними, с этими байтами, зато удобно!" - слышу возгласы. Друзья мои, не спешите делать выводы! Я же еще не закрыл данную тему!

Для удобства работы со стеком (и не только с ним) используется директива EQU (EQUivalent - эквивалент) (помните Finish equ $ ?). Так вот, с помощью данной директивы мы можем очень просто получить доступ к параметрам, занесенным в стек. Теперь внимательно смотрите, что мы сделаем с приведенным выше примером, используя EQU:

__________

...
(1) push offset Message1
(2) push offset Message2
(3) call Print_string
...
(4) Print_string proc
(5) mov bp,sp
(6) add bp,2
(7) mov ah,9
(8) mov dx,Mess1
(9) int 21h
(10) mov dx,Mess2
(11) int 21h
(12) ret 4
(13) Print_string endp
...
(14) Message1 db 'Привет!$'
(15) Message2 db 'Это я!$'

(16) Mess1 equ [BP+2]
(17) Mess2 equ [BP]

__________

Теперь строки выведутся в так: "Привет", а затем "Это я". Строки (16) - (17) места в памяти НЕ занимают. При ассемблировании MASM / TASM заменит строки

mov dx,Mess1
и
mov dx,Mess2

на

mov dx,[BP+2]
mov dx,[BP]

соответственно. Т.е. стоит один раз позаботиться, а затем все будет просто! Еще нужно будет вам немного "руку набить": написать три-четыре подобные программы и - все! Вы будете счастливы!

Смотрите, как мы делаем подобные вещи в нашей оболочке (DATA.ASM):

Height_X equ [bp+18] ;высота рамки
Width_Y equ [bp+16] ;ширина рамки
Attr equ [bp+14] ;атрибуты рамки
и т.д.

Получаем же доступ таким образом (DRAW_FRAME, DISPLAY.ASM):

mov ax,Height_X
mov ax,Attr
и т.п.

Один раз посчитали, записали и пользуемся! Еще раз обращаю ваше внимание (т.к. много ошибок происходит): Heigt_X заносится в стек первой, а считываем ее последней (Height_X equ [bp+18]). Возврат из процедуры RET 20, т.к. заносим 20 байт. Ошибки (упущения) у вас, конечно, будут. Но сразу проверяйте: а правильно ли вы передали и получили параметры в/из стека, а также верно ли вы выходите из процедуры.

Поверьте, это очень удобно. Я скажу более. При программировании под Windows мы будем постоянно передавать параметры в стеке, причем очень много! Так что, готовьтесь и привыкайте!

__________

Еще несколько слов о данной процедуре.

Обратите внимание, на параметр Other. В нем мы будем передавать (и уже передаем один) параметры, которые требуют проверки на "да" или "нет". Для этого используется всего один бит данной переменной. В нашем примере мы, например, решили, что нулевой бит (крайний справа) будет указывать на то, стоит ли перед выводим рамки копировать часть экрана или нет (ниже рассмотрим). В дальнейшем, когда мы добавим функций, будем проверять уже не байты, а биты.

Допустим, третий бит параметра Other указывает на то, выводить подчеркнутую линию или нет (например). Если третий бит установлен, то выводим, а если нет, то - нет. Т.о. один байт в Ассемблере может нести 8 различных параметров (в байте, как мы помним, 8 бит). Потом будем серьезно работать с битами. Для этого и изучали мы двоичную систему счисления (если вы ее, конечно, помните. А если нет - то не страшно. Скоро вспомните всё!) Удобно ли использовать 8 бит одного байта для передачи параметров? Ес-сно!

__________

Обратите внимание, как мы заносим в стек смещение строки:

push offset Mess_head

Просто, да?

__________

Еще обращаю ваше внимание, как мы делаем:

push offset Mess_head ;надпись вверху рамки (если 0, то не выводить)
push 1E00h ;цвет надписи вверху рамки

Т.е. если заносим просто нуль, то строка выводиться не будет. Может ли быть такое, что смещение некоторой строки будет нулевым? Вряд ли...

Тем не менее, если нам не нужно выводить строку, то атрибут строки все равно надо заносить (просто любое число). Если мы этого не сделаем, то стек останется невыровненным. Т.к. занесли мы 16 байт, а процедура вытащит 20. Что получается? Нарушение работы стека...

Может, это сложно читается. Если так, то смотрите файл-приложение. Исследуйте. Все действительно очень просто!

__________

Обратите внимание, как мы просто вычисляем длину строки. Но это только в том случае, если длина строки известна:

Mess_quit db 'Ассемблер',0
Mess_quitl equ $-Mess_quit

Причем, последняя строка занимать памяти не будет! В отладчике строка

mov dx,offset Mess_quitl

будет выглядеть как

mov dx,9

Зачем это нужно? Вспомните ситуацию со строками вида mov ax,23+5*2. Это удобно. Более того, добоавив или удалив что-либо в/из строки Mess_quit, программа-ассемблер (MASM/ TASM) автоматически посчитает ее размер. А иначе нам придется делать это самостоятельно...


Процедуры Copy_scr / Restore_scr (DISPLAY.ASM).

Прежде, чем вывести на экран рамку (окошко), нам нужно сохранить ту информацию (те символы), которые будут потеряны (затерты выводимым окошком). Попробуйте нажать в Norton Commander'е F5, а затем ESC. Вы задумывались, как так окошко появляется на экране, а затем исчезает, восстановив затертые символы. Создается ощущение того, что окошко просто располагается поверх чего-то другого. Кажется, мелочь. А это все, дорогие мои, надо собственными рученьками делать (точно так же, как и восстановление пользовательского экрана (при нажатии на Ctrl+O)). А как иначе вы думали? Мы еще будем и тень от окошка рисовать потом.

Для этой цели напишем две процедуры: Copy_scr (копирование в буфер) и Restore_scr (восстановление) (DISPLAY.ASM).

Принцип такой: получаем DH (ряд) с которого следует начать сохранение, причем DL (колонка) будет всегда нулевым (так удобнее для программиста). В AX заносится количество рядов, которое нужно будет сохранить относительно DH.
Сперва нужно получить линейный адрес (просто вызовем известную нам процедуру Get_linear, хотя можно было бы самим умножить. Но зачем? Увеличивать код?):

xor dl,dl ;Обнулим DL на всякий случай. Теперь DH = ряд, DL = 0
call Get_linear ;Получим линейный адрес

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

mov bl,160 ;Получим количество байт, которые нужно копировать
mul bl
mov cx,ax ;Их - в CX (будем использовать CX как счетчик)

AL у нас указывается на входе. MUL BL умножает BL на AL, результат - в AX, который переносим в CX.

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

Копировать будем целыми рядами, т.е. от начала левого угла до конца (DL от 0 до 79; для этого мы и обнуляя DL "на всякий случай"). Копируется столько рядов, сколько будет занимать окошко.

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

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


Новый оператор SCAS

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

Название Перевод Применение Процессор
SCAS SCAn String - сканирование строки Поиск символа 8086

Данный оператор имеет две разновидности. Какие-какие? Все верно! Вы уже мыслите по аналогии с MOVS и STOS.

Вот они: SCASB и SCASW. Первый служит для поиска первого попавшегося байта, а второй - первого попавшегося слова.

При этом ES:DI должен содержать адрес строки.

Пример:

________

...

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

(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 знаем.

2. Есть ли REPE (по аналогии с JNE / JE)?

2. Что будет, если в приведенном выше примере мы занесем в AL 13?

3. Что будет, если мы в CX занесем, например, 7?

Итак, оператор REPNE (REPeat in Not Equal - повторять, если не равно) сканирует строку (повторяет) до тех пор, пока число в AL/AX не будет найдено в строке. Он обычно используется для поиска первого символа в отличие от оператора REPE (REPeat if Equal - повторять, если равно).

Пример с оператором REPNE:

__________

...

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

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

__________

В данном случае после выполнения строки (4) DI будет указывать на адрес следующего за цифрой 6 байта (т.е. 1). Данный префикс (REPE) обычно используется для поиска первого символа не равного тому, который содержится в AL/AX. В принципе, вопросов не должно возникать.

Однако, вы пока что не имеете понятия, где бы можно было применить данные операторы. Могу сказать только одно: нужна практика. Я в свое время изучал Ассемблер по книге Джордейна. Сперва никак не мог понять принцип языка. Я брал просто примеры из книги и печатал их. Со временем появлялись мысли, алгоритмы, начал понимать, что представляет из себя Ассемблер. Так по-тихоньку и научился...

В первой части книги я писал так (выдержка):

"...Может быть, Вам показался язык Ассемблера чрезвычайно сложным, но это, поверьте, с первого взгляда. Вы должны научиться строить алгоритм программы на Ассемблере в голове, а для этого нужно будет написать несколько программ самостоятельно, опираясь на информацию из данной книги. Я буду постепенно учить Вас мыслить структурой Ассемблера, учить составлять алгоритмы, программы, используя операторы языка. После изучения очередной главы, Вы будете чувствовать, что постепенно начинаете осваивать Ассемблер, будет становиться все проще и проще…
Например, если Вы знакомы с Бейсиком, то, ставя перед собой задачу написать программу, выводящую 10 слов "Привет", Вы будете использовать операторы FOR, NEXT, PRINT и пр., которые тут же появятся в Ваших мыслях. Вы строите определенный алгоритм программы из этих операторов, который в какой-то степени применим только к Бейсику. Тоже самое и с Ассемблером. При постановке задачи написать ту или иную программу, Вы мысленно создаете алгоритм, который применим к Ассемблеру и только, т.к. языков, похожих на Ассемблер, просто не существует. Моя задача - научить Вас создавать в уме алгоритмы, применимые к Ассемблеру, т.е. научить "мыслить на Ассемблере"..."

Так что, не волнуйтесь! Все нормально!


Вернемся к оболочке.

Мы вкратце рассмотрели оператор SCAS потому, что теперь мы используем его в нашей оболочке для подсчета длины строки (Count_strmid, DISPLAY.ASM). Как?

У нас признаком окончания строки является символ ASCII 0. Считаем количество символов в строке для того, чтобы вывести строку в центре ряда. В дальнейшем мы будем часто вызывать данную процедуру. Давайте поподробней рассмотрим ее.

___________

(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 указывает на следующий символ за найденным 0

;SI=начало строки
;DI=конец строки+1

(7) sub di,si ;DI=DI-SI-1 = длина строки
(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 и еще вычесть 1 (7) - (8):

DI=DI-SI-1=длина строки

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

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

DL=(80/2)-(DI/2)

Теперь DL содержит колонку, с которой следует начинать выводить строку. Это и будет центр ряда.

____________

Вывод строки на экран путем прямого отображения в видеобуфер (Print_string, DISPLAY.ASM).

______

Print_string proc
(1) call Get_linear ;Получаем линейный адрес строки

Next_symstr:
(2) lodsb ;Получаем очередной символ строки
(3) or al,al ;Это 0 (конец строки?)
(4) jz Stop_outstr ;Да - выходим...
(5) stosw ;Иначе заносим в видеобуфер атрибут (AH) и символ (AL)
(6) jmp short Next_Symstr ;Следующий символ

(7) Stop_outstr:
(8) ret
Print_string endp

______

Перед вызовом данной процедуры мы должны указать в DS:SI адрес строки для вывода, DX - координаты для вывода (DH - столбец, DL - строка (ряд)), AH - атрибуты выводимой строки.

Сперва мы вызовем сразу процедуру перевода DX в линейный адрес. Зачем? Так ведь мы замедляем работу. Во-первых, это делается для удобства написания программы. Как вы думаете, было бы удобно постоянное вычислять самим линейный адрес перед тем, как вывести строку? И вообще, что удобней указать строку/столбец или одно число (линейный адрес)? Конечно же первый вариант предпочтительней. Ну а во-вторых, скорость не настолько уж падает. Я бы даже сказал, что на глаз на 8086 это абсолютно незаметно (я раньше писал оболочку именно на 8086 процессоре). Так что, можете быть спокойны!

Строка должна быть ASCIIZ (т.е. заканчиваться символом 0). Конечно, вы можете придумать другое ограничение строки (например, как у функции 09 - '$'). Но вряд ли это будет удобно...

Понять работу данной процедуры труда не составит. Все элементарно!

_______

Обратите внимание, как у нас организована проверка нажатых клавиш (MAIN_PROC, MAIN.ASM), а также посмотрите процедуру Quit_prog в этом же файле. В дальнейшем мы усовершенствуем вывод рамок на экран с запросом "Да" / "Нет". Эти окошки, как правило, аналогичны.

_______

Что еще? Посмотрите, как просто мы показываем пользовательский экран (при нажатии на Ctrl+F5) (MAIN_PROC, MAIN.ASM). Очень быстро и просто!


Вот, вроде, и рассмотрели еще один выпуск с оболочкой. На мой взгляд, вы уже можете написать самостоятельно оболочку типа Norton Commander, используя программу helpassm (у кого нет - возьмите на нашем сайте). Моя задача теперь заключается в том, чтобы показать вам "подводные камни", алгоритмы, и пр. Как видите, мы уже написали несколько мощных процедур (например, Draw_frame, Print_string), которыми вы можете пользоваться. Просто вставляйте их в собственные программы и вызывайте на здоровье! Сложно? Да, в общем-то, нет... Просто много времени уходит на написание процедуры. Зато какой выигрыш мы получаем во времени и в размере!

И кто сказал, что Ассемблер сложный язык?

До встречи через неделю!

P.S. Скоро Новый год...


С уважением,

Автор рассылки:

Калашников Олег

www.oleg77.newmail.ru

E-mail:

assembler@beep.ru

UIN (Тетя Ася):

68951340

(С) Авторское право. Запрещается использование материала из рассылки в коммерческих целях без письменного согласия автора.


http://subscribe.ru/
E-mail: ask@subscribe.ru
Поиск

В избранное