Выделение памяти под переменные и массивы

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

Perem db 73

Мы задали переменную, назвав ее именем perem, попросили транслятор выделить для этой переменной байт памяти (директива db) и загрузить в этот байт число 73. То есть исходное значение нашей переменной – 73, по ходу работы программы это значение может меняться. Кроме директивы db (defined byte)есть еще директивы dw (defined word) (выделить в памяти слово), dd(defined double word)(выделить двойное слово) и другие. Например:

Cursor db 12

X1 dw 542h

counter dd 37*14

Хорошо, переменные мы задали, а как теперь с ними работать? Это тоже несложно:

mov ax, x1; записываем в ах значение переменной х1

add cursor, 2; прибавляем двойку к значению переменной cursor

mov bl, byte ptr counter+3; записываем в bl значение старшего (третьего) байта
; переменнойcounter

Последняя команда требует более развернутого комментария. Переменная counterописана у нас как двойное слово (4 байта), а регистр bl имеет формат 1 байт. Для того чтобы транслятор не выдавал предупреждения о «несогласованности типов операндов», а понял, что программист точно знает, чего он хочет, и вставлена директива byte ptr. Если бы в команде было бы написано не counter + 3, а просто counter, мы бы обратились не к третьему, а к нулевому (младшему) байту нашего двойного слова.

Совершенно аналогично задаются массивы:

note db 40, 35, 27, 90; мы задали массив из четырех чисел

Обратиться, например, ко второму элементу нашего массива (число 27) можно одним из следующих способов:

mov dh, note+2

или

mov si, offset note;после этого в si смещение нулевого элемента массива

mov dh, [si+2]

или

Mov si, offset note

add si, 2; теперь в si смещение второго элемента массива

mov dh, [si]

Недостаток (или достоинство?) последнего варианта в том, что мы портим si и он теперь указывает не на начало нашего массива. Отметим также, что +2 во всех этих трех вариантах означает «смещение на два байта относительно начала массива» и далеко не всегда является смещением второго элемента массива. Так, например, если у нас задан массив слов:

Sl dw 707, 12, 421, 0, 1234

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

mov ax, sl+4 ;начало массива плюс четыре байта

Можно задавать и символьные массивы. Делается это так:

strochka db ‘Это строка символов’

Транслятор сам переведет все символы, записанные между апострофами, в их ASCII коды и мы получим массив из 19 байт (17 букв и 2 пробела).

Иногда нужно задать большой массив, например, состоящий из 1000 элементов, причем исходное состояние элементов нас не интересует. То есть всем этим элементам можно присвоить значение 0 (или 117, или 66, …). Такая ситуация возникает, например, когда надо создать в памяти буфер, в который впоследствии будет записываться информация, допустим из какого-то файла. Не писать же нам тысячу нулей через запятую. На этот случай имеется директива dup (повторять):

mass dw 1000 dup (117);то что повторяется задается в скобках

Можно создавать и более сложные конструкции:

out­_string db ‘nomber of files =’, 4 dup (?), 10,13,’$’

Это заготовка некой строки для вывода на экран. 4 dup (?)– выделяет 4 байта, в которые программа запишет ASCII коды цифр, из которых состоит найденное «число файлов» (вместо ? можно было написать 0, результат был бы тем же). 10 и 13 – управляющие коды, переводящие курсор в начало новой строки экрана. ‘$’ – означает конец выводимой строки.

В заключение приведем еще один пример массива, при задании которого часто встречаются ошибки:

Pause db 11, 22, 33, 44, 55, 66, 77, 88

Db 99, 100, 32, 0

Как видно из этого примера, если мы продолжаем массив на новой строчке, то надо начать эту строчку с директивы (у нас db), а на предыдущей строчке после последнего элемента запятая не ставится!

EXE и COM программы

Как известно, любая программа, в конце концов, транслируется в исполняемый файл, имеющий либо EXE, либо COM формат.

В EXE – программе создаются отдельные сегменты для кода, для данных и для стека, причем, если программа большая сегментов для данных и для кода может быть даже несколько. Все эти сегменты, так или иначе, описывает программист, при написании программы. Сегмент стека в принципе можно не описывать (не создавать), в этом случае программа будет работать со стеком DOS, хотя tlinkпри трансляции выдаст предупреждение (Warning: no stack), которое можно проигнорировать.

В COM – программе для кода, данных и стека отводится один общий сегмент. То есть COM формат используется при написании программ небольшого объема, полностью помещающихся в 64 Кбайта памяти. Мы в дальнейшем, в основном, будем придерживаться именно этого формата.

Загружая в память на исполнение как COM, так и EXE программу, DOS, помимо самой программы, размещает в памяти еще и так называемый «префикс программного сегмента» (PSP). Занимает PSP 256 байт памяти и в него DOS записывает различную служебную информацию. Для EXE программ DOS сама выделяет память под PSP, то есть программист об этом заботиться не должен. А вот для COM программ ситуация иная, здесь программист сам должен выделить память под PSP с помощью директивы org 100h(или org 256) .

Ситуация в памяти после загрузки на исполнение EXE программы показана на рис. 2.9.

Как видно из рисунка 2.9 сегментный регистр ds сразу после загрузки указывает «не туда куда надо» (ds указывает на первый байт PSP, а должен указывать на первый байт сегмента данных). Именно поэтому в начале EXE программы ds нужно настроить надлежащим образом.


ds; es à  
    PSP  
ss à  
    сегмент стека  
ss:sp à  
    сегмент данных  
cs:ip à  
  программа (сегмент кода)

рис 2.9

Стандартно EXE программа пишется следующим образом:

stack segment stack; описание сегмента стека

; Первое слово stack– название сегмента, можно было назвать сегмент и по другому,
; например, vasya.Кстати, транслятор нас предупредит, что мы используем
; зарезервированное слово stack не по назначению. Это предупреждение можно спокойно
; проигнорировать. Второе слово stack говорит транслятору, а потом и DOS
; что это сегмент стека.

db 100 dup (0); выделяем под стек 100 байт (можно больше, можно меньше)

stack ends; конец сегмента стека

data segment; описание сегмента данных

; здесь располагаются переменные и массивы

Data ends

code segment; описание сегмента кода

Assume cs:code, ds:data

; Это директива для транслятора. Объяснять ее назначение долго и сложно. Проще
; поверить, что она необходима.

start:; программа начинается с метки (любой!)

Mov ax, data

mov ds, ax ; настраиваем ds на начало сегмента данных

; здесь пишется программа

Mov ah, 4ch

int 21h; стандартный выход из программы

Code ends

end start ; start –метка, с которой мы начали программу

Дадим необходимые пояснения. Все что находится в строке программы после «точки с запятой» транслятор понимает как комментарий. Сегменты stack, dataи code можно было назвать и другими именами, но тогда мы должны использовать в директивах endsи assumeи команде mov ax, dataэти другие имена. Изменять второе слово stackнельзя, тогда у нас не будет создан сегмент стека. Причины настройки dsна начало сегмента данных объяснялись в этом разделе выше.Остается выяснить, зачем нужен стандартный выход из программы.

Когда процессор выполняет нашу программу, он «понятия не имеет» где она кончается. Он просто выбирает команды из памяти и их выполняет. Выполнив последнюю команду нашей программы, процессор продолжит выбирать информацию из памяти и буде пытаться выполнять эту информацию в качестве команд. Поскольку после последней команды программы в памяти, скорее всего, «мусор», такая ситуация почти наверняка приведет к зависанию системы. Поэтому в конце программы организуется корректный возврат управления в ту среду (DOS NAVIGATOR или Far или ….), из которой была запущена наша программа.

Ситуация в памяти после запуска СОМ программы представлена на рис. 2.10.

cs; es; ds; ss à  
    PSP  
cs:ip à  
    программа  
ss:sp à  

рис. 2.10

Как видно из рисунка 2.10 DOS настраивает все сегментные регистры на первый байт PSP. В регистр ipпрограммист, выделяя директивой org 100hместо под PSP, заносит 100h. После этого пара cs:ipуказывает на первую команду программы. В указатель стека spDOS загрузит число fffeh. После этого пара ss:spуказывает на ячейку, отстоящую почти на 64К от начала PSP, с тем, чтобы стек располагался как можно дальше от программы и данных и не мог их испортить при своем росте в сторону младших адресов (во всяком случае, уменьшается вероятность такой ситуации).

Стандартно СОМ программа пишется таким образом:

Code segment

Assume cs: code, ds: code

Org 100h

start:

jmp begin; перепрыгиваем область данных

; здесь можно располагать переменные и массивы

begin:

; здесь располагается программа

Mov ah, 4ch

int 21h; стандартный выход из программы

; здесь можно располагать переменные и массивы

Code ends

End start

Дадим необходимые пояснения. Здесь описывается только один сегмент. Поскольку внутри этого сегмента располагаются и данные и программа, программист обязан не допускать ситуации, в которой процессор начал бы выполнять данные как команды. Поэтому переменные и массивы либо перепрыгивают командой jmp, либо располагают после выхода из программы.

Наши рекомендации