Калашников.ru - Ассемблер? Это просто!.. (Выпуск № 004) Здравствуйте, уважаемые подписчики!

Сегодня у нас:

План рассылки:

Новости

Теория

Еще немного о сегментации памяти

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

Программка для практики

Предложение по ведению рассылки

Новости

Новость первая.

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

Новость вторая.

Я открыл свой сайт (наконец-то!), на котором можно скачать кое-какие программы (например, TASM), найти ответы на часто задаваемые вопросы по Ассемблеру, скачать примеры программ в DOS-формате с пояснениями, которые публикуются в наших рассылках, и кое-что еще. Но прошу вас не злоупотреблять перекачкой примеров, т.к. вы должны набирать все операторы сами. Сайт открылся 17 июня 2000 года, и в данный момент на нем, вероятно, вы найдете не так много, как ожидали. Тем не менее, я стараюсь регулярно заносить туда новую информацию. Вы уж, пожалуйста, пока не критикуйте сильно!

Немного теории.

Итак, давайте подведем с вами итог. Вы уже знаете:

Шестнадцатеричную систему счисления;

Двоичную систему счисления;

Некоторые регистры микропроцессоров Intel 8086/8088/80186;

Основы сегментации памяти в DOS;

Операторы Ассемблера:

org (с какого места отсчитывать смещение)

mov (загрузка данных в регистр или память)

add (сложение)

sub (вычитание)

inc (увеличение на единицу)

int (вызов прерывания)

Функцию 09h прерывания 21h (вывод строки на экран в текущую позицию курсора);

Функцию 10h прерывания 16h (ожидание нажатия клавиши).

В принципе, довольно много, чтобы начать серьезное и интенсивное обучение языка Ассемблера. Самое интересное только начинается! Если что-то из вышеперечисленного вам не совсем понятно, то спрашивайте. Просмотрите еще раз прошлые выпуски. Больше заострять внимание на том, что в них находится мы не будем. Однако, если ко мне будет поступать много вопросов по прошлым выпускам, то я обязательно на все отвечу. Итак, теперь только вперед!

Еще немного о сегментации памяти.

Давайте возьмем часть примера, который мы уже рассматривали, но кое-что в нем упустили.

(1) ...

(2) mov ah,9

(3) mov dx,offset My_string

(4) int 21h

(5) ....

(6) My_string db 'Ура!$'

(7) ...

Опустим некоторые операторы: строки (1), (5) и (7). Их вы уже знаете.

В строке (3) загружаем в регистр DX АДРЕС строки в памяти. Обратите внимание на запись: mov dx,offset My_string. Вы уже знаете, что оператор mov загружает в регистр число. Например:

mov cx,125

В строке (3) мы видим пока еще неизвестный нам оператор offset. Что же он делает? И почему нельзя записать вот так: mov dx,My_string?

Offset по-английски - это смещение. Когда, при ассемблировании, Ассемблер дойдет до этой строки, он заменит offset My_string на АДРЕС (смещение) этой строки в памяти. Если мы запишем mov dx,My_string (хотя, правильнее будет mov dx,word ptr My_string, но об этом позже), то в DX загрузится не адрес (смещение), а первые два символа нашей строки (в данном случае "Ур"). Почему два? Вы не знаете? Потому, что DX - шестнадцатиразрядный регистр, в который можно загрузить два байта. А один символ, как вы уже знаете, всегда один байт.

Можно записать и так: mov dl,My_string (здесь правильнее будет mov dl,byte ptr My_string). В этом случае что будет находится в DL? Символ "У"! Потому, что DL восьмиразрядный регистр и может хранить только один байт.

Несколько слов про записи вида mov dl,byte ptr My_string и mov dx,word ptr My_string.

Byte (думаю, все знают) - это байт. Word - слово (два байта). Посмотрите внимательно на приведенные выше строки. Вы заметите, что когда используется восьмиразрядный регистр (DL), мы пишем byte. А когда шестнадцатиразрядный (DX) - word. Это указывает Ассемблеру, что мы хотим загрузить именно байт либо слово.

Вспомним, что в DOS для формирования адреса используется сегмент и смещение. Данный пример - не исключение. Для формирования адреса строки "Ура!$" используется пара регистров DS (сегмент) и DX (смещение). Почему же мы ничего не загружаем в DS? Дело в том, что при загрузке *.com-программы в память (а мы пока создаем только такие), все сегментные регистры принимают значение равное тому сегменту, в который загрузилась наша программа (в т.ч. и DS). Поэтому нет необходимости загружать в DS сегмент строки (он уже загружен). Программа типа *.com всегда занимает один сегмент, поэтому размер программ такого типа ограничен 64 килобайтами. Помните, почему?

Программы, написанные на "чистом" Ассемблере, очень компактны. И 64 килобайта для них довольно большой объем.

Помнится, писал когда-то я антивирусную оболочку типа "а-ля Нортон Коммандер" на Ассемблере. Так она заняла у меня примерно 40 килобайт, хотя и не выполняла всех функций NC, но делала кое-что другое. Еще пример: Volcov Commander поздних версий. Практически копия NC, но занимает всего 64000 байт (в отличие от Нортона). Я подозреваю, что писали ее если не на "чистом" Ассемблере, то хотя бы большую часть кода так точно. Да и работает Volkov гораздо быстрее Нортона.

Вернемся. Если есть желание поэкспериментировать, то попробуйте перед вызовом 21h-ого прерывания загрузить в DS какое-нибудь число. Например, так:

...

mov dx,offset My_string

mov ax,10h

mov ds,ax

mov ah,9

int 21h

...

Вы увидите, что программа выведет не нашу строку, а какой-то "мусор" на экран, хотя в DX мы и загружаем адрес нашей строки, но сегмент другой. Только не забудьте восстановить DS после выполнения данной функции:

mov ax,cs

mov ds,ax

Итак, полное описание:

Функция 09h прерывания 21h - вывод строки символов на экран в текущую позицию курсора:

Вход: AH = 09hDS:DX = адрес ASCII-строки символов, заканчивающийся '$'

Выход: ничего Ну, разобрались с этим окончательно...

Новые операторы.

Создание циклов.

Что такое цикл? Допустим, нам нужно выполнить некоторый код программы несколько раз. Возьмем, к примеру, вывод строки функцией 09h прерывания 21h:

Пример 1.

mov ah,9

mov dx,offset Str

int 21h

mov ah,9

mov dx,offset Str

int 21h

mov ah,9

mov dx,offset Str

int 21h

Этот участок кода выведет 3 раза на экран некую строку Str. Код получается громоздким, неудобно читать. Размер программы разрастается... Для выполнения подобных примеров используется оператор loop (вспоминаем, как мы оформляем новые операторы):

Команда

Перевод (с англ.)

Назначение

Процессор

LOOP метка loop - петля Организация циклов 8086 Количество повторов задается в регистре CX (счетчик). Вот как можно использовать этот оператор на практике (изменим Пример 1):

Пример 2:

(1) mov cx,3

(2) Label_1:

(3) mov ah,9

(4) mov dx,offset Str

(5) int 21h

(6) loop Label_1

(7) ...

В строке (1) загружаем в CX количество повторов (отсчет будет идти от 3 до 0). В строке (2) создаем метку (Label - метка). Далее (строки (3)-(5)) выводим сообщение. И в строке (6) оператор loop уменьшает на единицу CX и, если он не равен нулю, переходит на метку Label_1 (строка (2)). Итого строка будет выведена на экран три раза. Когда программа перейдет на строку (7), регистр CX будет равен нулю. В результате код программы уменьшается почти в три раза по сравнению с Примером 1.

Удобно? Без вопросов!

Тренироваться будем на практике, а теперь следующий оператор:

Команда

Перевод (с англ.)

Назначение

Процессор

JMP метка jump - прыжок Безусловный переход 8086 Команда jmp просто переходит на указанную метку в программе:

(1) mov ah,9

(2) mov dx,offset Str

(3) int 21h

(4) jmp Label_2

(5)

(6) add cx,12

(7) dec cx

(8) Label_2:

(9) int 20h

В результате строки (5) - (7) работать не будут. Программа выведет сообщение на экран, а затем jmp перейдет к строке (8), после чего программа завершится.

Команда

Перевод (с англ.)

Назначение

Процессор

DEC приемник decrement - декремент Уменьшение на 1 8086 Оператор dec уменьшает значение приемника на 1:

mov ah,12 ---> AH=12

dec ah ---> AH=11

С помощь данного оператора можно также создавать циклы, которые будут работать быстрее оператора loop. Следующий пример будет работать также, как Пример 2, только чуть-чуть быстрее:

Пример 3:

(1) mov cx,3

(2) Label_1:

(3) mov ah,9

(4) mov dx,offset Str

(5) int 21h

(6) dec cx

(7) jnz Label_1

Не обращайте внимание на строку (7). Мы ее рассмотрим позже. Я привел этот пример для того, чтобы показать, что один и тот же прием в Ассемблере можно выполнить разными операторами. И чем лучше программист владеет ими, тем компактнее и быстрее программа будет работать. Поэтому и получается, что разные программисты пишут на одном языке, но скорость и объем программы разные. В процессе обучения, я буду также учить вас оптимизировать программы.

Программка для практики.

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

(1) CSEG segment

(2) org 100h

(3) Begin:

(4) mov ax,0B800h

(5) mov es,ax

(6) mov di,0

(7) mov al,1

(8) mov ah,31

(9) mov cx,2000

(10)

(11) Next_face:

(12) mov es:[di],ax

(13) add di,2

(14) loop Next_face

(15)

(16) mov ah,10h

(17) int 16h

(18) int 20h

(19) CSEG ends

(20) end Begin

Уфф! Длинная получилась. Прежде чем читать описание программы, попробуйте сами разобраться, что в итоге получится. Поверьте, это принесет вам пользу. Все ведь очень просто!

Теперь описание программы.

Строки с (1) по (10) и с (15) по (20) вы уже знаете. Объясню только новое.

Строка (11) - это метка, как бы "голова" нашего цикла. Строка (14) - "хвост" цикла. Все, что находится в пределах строк (10) - (14), является циклом. Сам цикл будет повторяться 2000 раз, для чего мы и заносим в CX число 2000 (строка (8)).

В строке (12) записываем в видеобуфер (0B800:DI) число в AX (это символ (строка (7) и атрибут (строка (8)). Итак, первый символ занесли. Что делаем дальше?

Дальше увеличиваем регистр DI на 2 для того чтобы перейти к адресу следующего символа. Почему на 2? Дело в том, что в видеобуфере один символ занимает 2 байта: сам символ и его атрибут. Т.к. символ у нас в AL, а атрибут в AH, и мы загрузили уже эти два байта в строке (12), то и увеличиваем DI (смещение) на 2.

DI теперь указывает на адрес для следующего символа. Осталось уменьшить счетчик (CX) на 1 и повторить. Что мы, собственно, и делаем в строке (14).

Все! Обратите внимание на скорость вывода символов при запуске программы.

Еще раз напоминаю: пожалуйста, печатайте все программы сами! Так вы быстрее освоите Ассемблер!

Предложение по ведению рассылки

Дорогие мои подписчики! Хочу вынести на ваш суд следующее предложение по построению наших уроков.

Мы можем начинать писать какую-нибудь серьезную программу на Ассемблере, попутно изучая сам язык. От вас требуется проявлять некоторую активность и присылать мне какую программу вы хотели бы написать. Только поактивнее, пожалуйста! Все варианты будут опубликованы в последующих рассылках, а вы уж проголосуете за ту или иную программу. Я надеюсь, что изучать язык таким образом будет гораздо интереснее. А вы как думаете?

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

С уважением,

Калашников Олег (Assembler@Kalashnikoff.ru).

[Следующий выпуск] [На главную страницу]

u="u496.71.spylog.com";d=document;nv=navigator;na=nv.appName;p=1; bv=Math.round(parseFloat(nv.appVersion)*100); n=(na.substring(0,2)=="Mi")?0:1;rn=Math.random();z="p="+p+"&rn="+rn;y=""; y+=""; y+=""; y+=""; d.write(y);if(!n) { d.write("

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