Еще немного о сегментации памяти.
Давайте возьмем часть примера, который мы уже рассматривали, но кое-что в нем упустили.
(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 = 09h DS:DX = адрес ASCII-строки символов, заканчивающийся '$' |
Выход: | ничего |
Новые операторы.