Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Assembler / P20

.pdf
Скачиваний:
52
Добавлен:
02.06.2015
Размер:
507.97 Кб
Скачать

20.Модульное программирование Термин "модульность" применительно к программированию имеет два смысла:

разбиение программы на подпрограммы;

размещение текста программы в различных файлах.

До сих пор мы создавали программы, которые умещались в одном файле. Для

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

какие имена являются общими для модулей;

как объединять логические сегменты (программные секции). В следующих разделах мы подробно изучим эти аспекты.

20.1. Директивы связи Рассмотрим пример.

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

вотдельном файле.

Вглавной программе, размещенной в файле main.asm определены две строки: String — исходная строка, завершенная знаком $, NewStr — преобразованная строка. Подпрограмма ToUpper, размещенная в файле sub.asm, преобразует те символы строки, которые являются строчными латинскими буквами, в соответствующие прописные буквы. Адрес строки String передается в регистре SI. Строка NewStr является глобальным объектом для обоих модулей (файлов). Так сделано в учебных целях. (Было бы логичнее адрес NewStr передавать в регистре DI.) При этом строка NewStr определена в файле main.asm. В этом же файле имеется директива PUBLIC NewStr, которая делает это имя доступным для программ в других файлах. В файле sub.asm с помощью директивы EXTRN NewStr:BYTE:50 транслятору сообщается, что имя NewStr определено в другом файле, имеет тип: байты и размер 50. Тип нужен для правильного кодирования команд (в данном случае указание типа излишне, так как в программе используется только адрес строки). Число 50 в нашем примере также можно было не указывать. Далее мы приведем пример, когда указание такого числа может принести пользу. Аналогичные директивы используются для имени подпрограммы ToUpper.

main.asm

INCLUDE macro.inc

.MODEL small

.STACK 100h

PUBLIC NewStr EXTRN ToUpper:PROC

.DATA

String DB "aB2",CRLFT NewStr DB 50 DUP(?)

.CODE start:mov ax,@data

mov ds,ax

message String ; Вывод исходной строки mov si, OFFSET String

call ToUpper ; Преобразование строки

2

message NewStr ; Вывод новой строки exit

END start

Подпрограмма, описанная в sub.asm, может быть реализована более эффективно с использованием так называемых строковых команд, которые мы изучим позднее. sub.asm

JUMPS

.MODEL small

EXTRN NewStr:BYTE:50 PUBLIC ToUpper

.CODE

; вход si – адрес строки, заканчивающейся $

ToUpper PROC

mov di, OFFSET NewStr

n:mov al,[si] ; Очередной символ строки - в AL

cmp al,'a' ; Если символ входит в диапазон 'a'-'z' jnae copy ; - преобразовать его

cmp al,'z' jnbe copy

sub al,'a' - 'A' ; Перевести символ в верхний регистр copy:mov [di],al ; Поместить символ в новую строку

inc si ; Переместить указатели inc di

cmp al,'$' ; Конец строки ? jne n

ret ToUpper ENDP

END ; Обратите внимание, что метки стартового адреса нет, ; потому что такой метки нет в файле

Вариант с использованием строковых команд:

ToUpper PROC

mov ax, @data mov es,ax cld

mov di, OFFSET NewStr

n:lodsb ; Очередной символ строки - в AL

cmp al,'a'

; Если символ входит в диапазон 'a'-'z'

jnae copy

; - преобразовать его

cmp al,'z'

 

jnbe copy

sub al,'a' - 'A' ; Перевести символ в верхний регистр

copy:stosb

; Поместить символ в новую строку

cmp al,'$'

; Конец строки ?

jne n

 

ret

 

ToUpper ENDP

 

Команды для получения исполняемого файла c:\tasm\bin\tasm /z/zi/la/m main+sub c:\tasm\bin\tlink /v/m main sub

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

3

Итак, в модуле, где имя определено, но должно быть "известно" другим модулям, используется директива PUBLIC имя. В модуле, для которого имя является внешним, используется другая директива: EXTRN имя: тип: количество.

Вдирективе EXTRN для имени указывается его тип:

для имен данных: BYTE, WORD, DWORD…

для имен процедур: NEAR, FAR, либо PROC (тогда NEAR или FAR определяется моделью памяти).

Если тип указан для имен данных, то полезно указывать также и количество

элементов, если количество опущено, то по умолчанию оно полагается равным одному. Упражнение. Посмотрите листинги и карту памяти для main.asm и sub.asm. Что

нового в них появилось благодаря использованию внешних имен? Что произойдет, если закомментировать директивы PUBLIC и EXTRN?

Если нужно указать, что внешними являются несколько имен, то в директивах PUBLIC

иEXTRN они перечисляются через запятую.

Вдирективе PUBLIC тип имени указывать не нужно, он и так известен транслятору из текста модуля. В директиве EXTRN тип имени нужен для генерации правильных кодов команд. Пусть, например, процедура ToUpper должна принудительно менять первый символ строки на символ '*'. Перед ret добавим команду mov NewStr, '*', а директиву

EXTRN NewStr:BYTE:50 заменим на EXTRN NewStr.

Упражнение. Что при этом произойдет?

Ответ. Трансляция пройдет без замечаний, а в отладчике мы увидим команду mov word ptr [0006], 002A. Коды символов в строке NewStr станут такими: 2A 00 32 0D 0A 24.

Если бы Ассемблер "знал", что NewStr — массив байтов, то была бы сгенерирована команда mov byte ptr [0006], 2A.

20.2. Сегментные директивы

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

эти директивы используются в литературе по языку Ассемблера (зачастую без особой необходимости);

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

Прежде всего, надо подчеркнуть отличие физического и логического сегмента. Мы уже знаем, что физический сегмент — область памяти размером 64 К, ее начальный адрес лежит на границе параграфа. Логический сегмент (программный сегмент, программная секция) — часть программы. Но связь между этими понятиями есть. Логический сегмент определяет область, адрес которой лежит в сегментном регистре.

Логический сегмент начинается с директивы

имя SEGMENT атрибуты

и завершается директивой

имя ENDS

Пример.

 

data1

SEGMENT

p

DW 2

 

q

DW 3

 

data1

ENDS

4

data2

SEGMENT

r DW 0

 

data2

ENDS

cseg SEGMENT

s:mov ax, data1 mov ds, ax mov ax, data2 mov es, ax mov ax, p add ax, q mov r,ax

mov ax, 4C00h int 21h

cseg ENDS END s

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

mov ax, data1 mov ds, ax

(Напомню, что команда mov ds, data1 недопустима, поэтому приходится использовать регистр AX как "промежуточное звено".)

Эту программу tasm отказывается транслировать. Для команды mov ax, p и двух следующих он выдает сообщение об ошибке:

**Error** s1.ASM(13) Can't address with currently ASSUMEd segment registers (Не могу адресовать с текущим предполагаемым сегментным регистром)

Исправление:

mov ax, ds:p add ax, ds:q mov es:r,ax

Программа нормально транслируется и работает. В листинге мы видим строку

15 0011 26: A3 0000r mov es:r,ax

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

Неудобно перед всеми именами переменных расставлять префиксы. Избежать этого можно, применяя директиву ASSUME. Перед строкой с меткой s вставим директиву:

ASSUME ds:data1, es:data2

Теперь префиксы можно снять. А в листинге мы увидим, что префикс добавляется автоматически:

16 0011 26: A3 0000r mov r,ax

Сама по себе директива ASSUME не загружает регистры DS и ES нужными значениями. Она дает Ассемблеру информацию о связи сегментного регистра с соответствующим сегментом.

Внесем в программу еще одно изменение: после команды mov r,ax добавим команды: cmp r, 4

jg t inc ax

t:

(Не следует искать в этих командах скрытого смысла.)

**Error** s1.ASM(18) Near jump or call to different CS

В сегменте кода появилась ссылка на символическое имя t. Чтобы избежать сообщения об ошибке нужно добавить директиву

ASSUME cs:cseg

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

5

ASSUME ds:data1, es:data2, cs:cseg

Сообщение об ошибке исчезнет.

20.3. Параметры директивы SEGMENT.

Полная форма директивы SEGMENT имеет вид:

имя SEGMENT align combine use ‗class‘

Здесь align — выравнивание, combine — объединение, use — использование, ‗class‘ — класс.

Выравнивание. Этот параметр может принимать значения: BYTE, WORD, DWORD, PARA, PAGE. Может возникает вопрос: как можно выравнивать сегменты на границы байта, если начало сегмента заведомо начинается с границы параграфа, т.е. адрес кратен 16. Ответ следующий: если есть несколько модулей, содержащих сегмент с одинаковым именем, и эти сегменты должны пристыковываться друг к другу последовательно (это определяется параметром combine), то часть сегмента из первого модуля будет выровнена на границу параграфа, а последующие части будут пристыкованы к первой части с «зазором», который определяется параметром align. Например, если align — это BYTE, то «зазора» не будет. Если align — это WORD, то «зазора» не будет или он составит один байт (если предыдущая часть сегмента «закончилась» на нечетном адресе).

Упражнение. В двух файлах с расширением .asm определите сегменты данных с одинаковыми именами и параметром align равным DWORD. Проследите в отладчике эффект выравнивания адреса при объединении сегментов. В одном файле такого эффекта не будет.

Решение.

файл s.asm

n

SEGMENT dword

k DB 2,3

n

ENDS

cseg

SEGMENT

ASSUME cs: cseg

s:mov ax, 4C00h

int 21h cseg ENDS END s

файл s1.asm

n SEGMENT dword p DB 1

n ENDS END

c:\tasm\bin\tasm /zi s+s1 c:\tasm\bin\tlink /v s s1 c:\tasm\bin\td s.exe

Объединение. Если сегменты с одним именем расположены в одном модуле, то они объединяются последовательно. Если же сегменты находятся в разных модулях, то их объединение регулируется параметром combine. Этот параметр может принимать следующие значения:

PRIVATE — сегмент не должен объединяться с другими сегментами.

COMMON — начальный адрес всех сегментов одинаков, т.е. они перекрываются. Общий размер сегмента — это размер самого большого сегмента с данным именем.

6

PUBLIC — происходит конкатенация сегментов с одним и тем же именем. Общий размер сегмента — сумма размеров всех таких сегментов.

STACK — конкатенация сегментов, причем SS:SP указывает при запуске на конец сегмента.

Использование. Этот параметр принимает два значения: use16 (используются 16-битовые сегменты, которые содержат до 64K кода или данных) и use32 (используются 32-битовые сегменты, которые содержат до 4Г кода или данных).

Класс. Если логическому сегменту назначено имя класса, то компоновщик собирает вместе все сегменты с одинаковым именем класса.

20.4. Модели памяти Это набор правил, по которым компилятор и компоновщик распределяют память для

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

Tiny — код, данные и стек в одном сегменте.

Small — код в одном сегменте, данные и стек в другом.

Compact — код в одном сегменте, данные в нескольких сегментах. Medium — код в нескольких сегментах, данные в одном сегменте. Large — код в нескольких сегментах, данные в нескольких сегментах.

При программировании в так называемом защищенном режиме используется всего одна модель — плоская.

Flat — то же, что tiny, но сегменты 32-разрядные.

.MODEL модель, язык.

20.5. Пример использования стандартных сегментных директив. Перепишем нашу первую программу.

sseg SEGMENT para stack use16 ‗stack‘ DB 100h DUP(?)

sseg ENDS

dseg SEGMENT word use16 ‗data‘

msg DB ―Hello, world!‖, 0Dh, 0Ah, ‗$‘ dseg ends

cseg SEGMENT word use16 ‗code‘ ASSUME cs:cseg, ds:dseg

start: mov ax, dseg mov ds, ax

mov dx, OFFSET msg mov ah,9h

int 21h mov ah,4Ch int 21h cseg ENDS END start

Группы.

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

7

нужно будет переадресовывать. При этом общий объем логических сегментов, входящих в группу не должен превосходить размер физического сегмента.

Директива имеет вид

имя_группы GROUP имя_сегмента1, имя_сегмента2, …

Пример группы мы видели уже в нашей первой программе. Сегмент данных и сегмент стека были объединены в одну группу с именем DGROUP. Встроенная переменная @data содержит адрес группы. Это соответствует директивам

DGROUP GROUP _DATA, STACK

20.6. Ассемблерный код, генерируемый компилятором Си.

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

bcc –S c31v0.c

Будет получен файл c31v0.asm. Переименуем его в c31v0_16.asm.

_DATA

segment word public 'DATA'

d@

label byte

d@w

label word

_DATA

ends

 

_BSS

segment word public 'BSS'

b@

label byte

b@w

label word

_BSS

ends

 

_DATA

segment word public 'DATA'

_A

label word

 

db

1

 

db

0

 

db

5

 

db

0

 

db

2

 

db

0

 

db

7

 

db

0

 

db

5

 

db

0

 

db

6

 

db

0

 

db

3

 

db

0

_DATA ends

_TEXT segment byte public 'CODE' assume cs:_TEXT,ds:DGROUP

_main proc near

;

;int main() {

push bp mov bp,sp sub sp,2 push si push di

;int i, j, k;

;for (i = 0; i < dimA; i++)

xor si,si

jmp short @1@4

@1@2:

;

; printf("%d ", A[i]);

8

;

 

 

 

mov

bx,si

 

add

bx,bx

 

push

word ptr DGROUP:_A[bx]

 

mov

ax,offset DGROUP:s@

 

push

ax

 

call

near ptr _printf

 

pop

cx

 

pop

cx

 

inc

si

@1@4:

 

 

 

cmp

si,7

 

jl

short @1@2

;

 

 

;

 

 

;

i = j = dimA - 1; k = 0;

;

 

 

 

mov

ax,6

 

mov

di,ax

 

mov

si,ax

 

mov

word ptr [bp-2],0

 

jmp

short @1@16

@1@6:

 

 

;

 

 

;while (j > 0) {

;if (A[j] == 5) {

mov bx,di

add

bx,bx

cmp

word ptr DGROUP:_A[bx],5

jne

short @1@15

;

 

;

B[k++] = &A[j];

;

 

mov

ax,di

add

ax,ax

add

ax,offset DGROUP:_A

mov

bx,word ptr [bp-2]

add

bx,bx

mov

word ptr DGROUP:_B[bx],ax

inc

word ptr [bp-2]

jmp

short @1@9

@1@8:

 

;

 

;while (j > 0 && A[--j] != 5) {

;A[i--] = A[j];

;

 

mov

bx,di

add

bx,bx

mov

ax,word ptr DGROUP:_A[bx]

mov

bx,si

add

bx,bx

mov

word ptr DGROUP:_A[bx],ax

dec

si

@1@9:

 

or

di,di

jle

short @1@11

dec

di

mov

bx,di

add

bx,bx

cmp

word ptr DGROUP:_A[bx],5

jne

short @1@8

@1@11:

 

;

 

9

;}

;if (j == 0 && A[0] == 5)

or

di,di

jne

short @1@14

cmp

word ptr DGROUP:_A,5

jne

short @1@14

;

 

;

B[k++] = &A[j];

;

 

mov

ax,di

add

ax,ax

add

ax,offset DGROUP:_A

mov

bx,word ptr [bp-2]

add

bx,bx

mov

word ptr DGROUP:_B[bx],ax

inc

word ptr [bp-2]

@1@14:

 

;

 

;

}

;

 

jmp

short @1@16

@1@15:

 

;

 

;else {

;i--;

dec

si

;

 

;

j--;

;

 

dec

di

@1@16:

 

or

di,di

jg

short @1@6

;

 

;}

;}

;for (j = 0; j <= i; j++)

 

xor

di,di

 

jmp

short @1@20

@1@18:

 

;

 

 

;

A[j] = -1;

;

 

 

 

mov

bx,di

 

add

bx,bx

 

mov

word ptr DGROUP:_A[bx],-1

 

inc

di

@1@20:

 

 

cmp

di,si

 

jle

short @1@18

;

 

 

;

printf("\n\n");

;

 

 

 

mov

ax,offset DGROUP:s@+4

 

push

ax

 

call

near ptr _printf

 

pop

cx

;

 

 

;

for (i = 0; i < dimA; i++)

;

 

 

 

xor

si,si

10

jmp short @1@24 @1@22:

;

; printf("%d ", A[i]);

;

 

mov

bx,si

 

add

bx,bx

 

push

word ptr DGROUP:_A[bx]

 

mov

ax,offset DGROUP:s@+7

 

push

ax

 

call

near ptr _printf

 

pop

cx

 

pop

cx

 

inc

si

@1@24:

 

 

cmp

si,7

 

jl

short @1@22

;

 

 

;

printf("\n");

;

 

 

 

mov

ax,offset DGROUP:s@+11

 

push

ax

 

call

near ptr _printf

 

pop

cx

;

 

 

;

for (i = 0; i < k; i++)

;

 

 

 

xor

si,si

 

jmp

short @1@28

@1@26:

 

;

 

 

;

 

printf("%p ", B[i]);

;

 

 

 

mov

bx,si

 

add

bx,bx

 

push

word ptr DGROUP:_B[bx]

 

mov

ax,offset DGROUP:s@+13

 

push

ax

 

call

near ptr _printf

 

pop

cx

 

pop

cx

 

inc

si

@1@28:

 

 

cmp

si,word ptr [bp-2]

 

jl

short @1@26

;

 

 

;

return 0;

;

 

 

 

xor

ax,ax

 

jmp

short @1@30

@1@30:

 

;

 

 

;

}

 

;

 

 

 

pop

di

 

pop

si

 

mov

sp,bp

 

pop

bp

 

ret

 

_main endp

 

_TEXT ends

 

_BSS

segment word public 'BSS'

_B

label word

 

db

14 dup (?)

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