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

Лекции по предмету «Информатика»
Информация о работе
  • Тема: 20. Модульное программирование - Термин "модульность" применительно к программированию
  • Количество скачиваний: 0
  • Тип: Лекции
  • Предмет: Информатика
  • Количество страниц: 17
  • Язык работы: Русский язык
  • Дата загрузки: 2014-05-05 16:32:01
  • Размер файла: 28.09 кб
Помогла работа? Поделись ссылкой
Информация о документе

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

Если Вы являетесь автором текста представленного на данной странице и не хотите чтобы он был размешён на нашем сайте напишите об этом перейдя по ссылке: «Правообладателям»

Можно ли скачать документ с работой

Да, скачать документ можно бесплатно, без регистрации перейдя по ссылке:

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 ; Преобразование строки
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: asmin asm /z/zi/la/m main+sub
c: asmin link /v/m main sub
Здесь оба файла с исходными текстами обрабатываются одной командой tasm. При компоновки выполняемый файл получает имя первого из объектных файлов, перечисленных в списке.
Итак, в модуле, где имя определено, но должно быть "известно" другим модулям, используется директива 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
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) Cant 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
а еще проще добавить к уже имеющейся директиве еще один параметр
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: asmin asm /zi s+s1
c: asmin link /v s s1
c: asmin d s.exe

Объединение. Если сегменты с одним именем расположены в одном модуле, то они объединяются последовательно. Если же сегменты находятся в разных модулях, то их объединение регулируется параметром combine. Этот параметр может принимать следующие значения:
PRIVATE — сегмент не должен объединяться с другими сегментами.
COMMON — начальный адрес всех сегментов одинаков, т.е. они перекрываются. Общий размер сегмента — это размер самого большого сегмента с данным именем.
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

Группы.
Удобно объединять несколько логических сегментов (с различными именами) в область, адрес которой находится в сегментном регистре. Тогда все эти логические сегменты будут использовать только одно содержимое сегментного регистра. Его не нужно будет переадресовывать. При этом общий объем логических сегментов, входящих в группу не должен превосходить размер физического сегмента.
Директива имеет вид
имя_группы 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]);
;
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:
;
; }
; 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("

");
;
mov ax,offset DGROUP:s@+4
push ax
call near ptr _printf
pop cx
;
; for (i = 0; i < dimA; i++)
;
xor si,si
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("
");
;
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 (?)
?debug C E9
?debug C FA00000000
_BSS ends
_DATA segment word public DATA
s@ label byte
db %d
db 0
db 10
db 10
db 0
db %d
db 0
db 10
db 0
db %p
db 0
_DATA ends
_TEXT segment byte public CODE
_TEXT ends
extrn _printf:near
public _A
public _B
public _main
end

Внимательно изучите этот файл. Нужно пояснить, что в сегменте данных DATA собраны инициализированные данные, а в BSS — неинициализированные.
Теперь оттранслируем программу с помощью 32-разрядного компилятора.
bcc32 –S c31v0.c
Будет получен файл c31v0.asm. Переименуем его в c31v0_32.asm.
.386p
model flat
ifndef ??version
?debug macro
endm
endif
?debug S "c3v0.c"
?debug T "c3v0.c"
_TEXT segment dword public use32 CODE
_TEXT ends
_DATA segment dword public use32 DATA
_DATA ends
_BSS segment dword public use32 BSS
_BSS ends
DGROUP group _BSS,_DATA
_DATA segment dword public use32 DATA
align 4
_A label dword
dd 1
dd 5
dd 2
dd 7
dd 5
dd 6
dd 3
_DATA ends
_TEXT segment dword public use32 CODE
_main proc near
?live1@0:
;
; int main() {
;
push ebp
mov ebp,esp
push ebx
push esi
push edi
mov esi,offset _A
;
; int i, j, k;
;
; for (i = 0; i < dimA; i++)
;
?live1@16: ; ESI = &_A
@1:
xor ebx,ebx
;
; printf("%d ", A[i]);
;
?live1@32: ; EBX = i, ESI = &_A
@2:
push dword ptr [esi+4*ebx]
push offset s@
call _printf
add esp,8
inc ebx
cmp ebx,7
jl short @2
;
;
; i = j = dimA - 1; k = 0;
;
?live1@48: ; ESI = &_A
mov eax,6
mov ebx,eax
xor edi,edi
;
; while (j > 0) {
;
?live1@64: ; EBX = i, EAX = j, ESI = &_A, EDI = k
test eax,eax
jle short @6
;
; if (A[j] == 5) {
;
@5:
cmp dword ptr [esi+4*eax],5
jne short @7
;
; B[k++] = &A[j];
;
mov edx,eax
shl edx,2
add edx,esi
mov dword ptr [_B+4*edi],edx
inc edi
jmp short @9
;
; while (j > 0 && A[--j] != 5) {
; A[i--] = A[j];
;
@8:
mov ecx,dword ptr [esi+4*eax]
mov dword ptr [esi+4*ebx],ecx
dec ebx
@9:
test eax,eax
jle short @10
dec eax
cmp dword ptr [esi+4*eax],5
jne short @8
;
; }
; if (j == 0 && A[0] == 5)
;
@10:
test eax,eax
jne short @12
cmp dword ptr [esi],5
jne short @12
;
; B[k++] = &A[j];
;
mov edx,eax
shl edx,2
add edx,esi
mov dword ptr [_B+4*edi],edx
inc edi
;
; }
;
jmp short @12
;
; else {
; i--;
;
@7:
dec ebx
;
; j--;
;
dec eax
@12:
test eax,eax
jg short @5
;
; }
; }
; for (j = 0; j <= i; j++)
;
?live1@240: ; EBX = i, ESI = &_A, EDI = k
@6:
xor eax,eax
cmp ebx,eax
jl short @14
;
; A[j] = -1;
;
?live1@256: ; EBX = i, EAX = j, ESI = &_A, EDI = k
@13:
mov dword ptr [esi+4*eax],-1
inc eax
cmp ebx,eax
jge short @13
;
; printf("

");
;
?live1@272: ; ESI = &_A, EDI = k
@14:
push offset s@+4
call _printf
pop ecx
;
; for (i = 0; i < dimA; i++)
;
xor ebx,ebx
;
; printf("%d ", A[i]);
;
?live1@304: ; EBX = i, ESI = &_A, EDI = k
@16:
push dword ptr [esi+4*ebx]
push offset s@+7
call _printf
add esp,8
inc ebx
cmp ebx,7
jl short @16
;
; printf("
");
;
?live1@320: ; EDI = k
push offset s@+11
call _printf
pop ecx
;
; for (i = 0; i < k; i++)
;
xor ebx,ebx
cmp edi,ebx
jle short @20
;
; printf("%p ", B[i]);
;
?live1@352: ; EBX = i, EDI = k
@19:
push dword ptr [_B+4*ebx]
push offset s@+13
call _printf
add esp,8
inc ebx
cmp edi,ebx
jg short @19
;
; return 0;
;
?live1@368: ;
@20:
xor eax,eax
;
; }
;
@23:
@22:
pop edi
pop esi
pop ebx
pop ebp
ret
_main endp
_TEXT ends
_BSS segment dword public use32 BSS
align 4
_B label dword
db 28 dup(?)
_BSS ends
_DATA segment dword public use32 DATA
s@ label byte
; s@+0:
db "%d ",0,10,10,0
; s@+7:
db "%d ",0,10,0
; s@+13:
db "%p ",0
align 4
_DATA ends
_TEXT segment dword public use32 CODE
_TEXT ends
public _A
public _B
public _main
extrn _printf:near
?debug D "C:BC5INCLUDE\_null.h" 8825 10304
?debug D "C:BC5INCLUDE\_nfile.h" 8825 10304
?debug D "C:BC5INCLUDE\_defs.h" 8825 10304
?debug D "C:BC5INCLUDEstdio.h" 8825 10304
?debug D "c31v0.c" 12910 21959
end

Отладку файла c31v0.exe нужно проводить с помощью 32-разрядного отладчика:
td32.exe c31v0.exe

20.7. Программы на языке Си, размещенные в нескольких файлах.
Большие программы на языке Си создает бригада программистов. Они распределяют между собой работу по написанию подпрограмм. Каждый программист создает свой файл с подпрограммами. Далее их нужно собрать вместе. Порядок работы здесь следующий.
Если в файлах нет ссылки на общие глобальные данные (Глобальных данных следует избегать), то нужно только обеспечить правильный вызов функций.

20.8. Исполняемые файлы формата com
Мы уже создавали com-файлы с помощью отладчиков. Посредством tasm и tlink мы создавали только exe-файлы. Теперь с их помощью научимся получать и .com-файлы. Расширение файла com расшифровывается так: COre iMage. Вольный перевод: образ в оперативной памяти. Мы видели, что exe-файлы содержат блок управляющей и настроечной информации, в листинге отмечалось специальными флажками, где коды объектного и загрузочного файлов будут различными. Содержимое com-файла — в точности те же коды, которые будут расположены после загрузки в оперативной памяти. Эти файлы занимают меньше дисковой памяти, быстрее загружаются в ОЗУ, но при их создании надо придерживаться некоторых ограничений.
Различия между exe- и com-файлами сведем в таблицу (табл. *.1)

Таблица *.1
Свойство exe-файл com-файл
Размер программы Любой Не превышает 64 Кбайта
Стек Определяется сегмент стека. Стек определяется при загрузке.
SP = FFFE.
Данные Обычно определяется сегмент данных. Данные размещаются в кодовом сегменте.
Содержимое
сегментных
регистров CS — указывает на сегмент кода,
SS — указывает на сегмент стека,
DS, ES — указывают на PSP CS, SS, DS, ES —
указывают на PSP
Итак, из первой строки таблицы ясно, что размер com-файла не превышает 64K. Но в Windows 95 размер файла command.com составляет 92870 байтов! На самом деле это exe-файл (нетрудно убедиться, что его первые два байта образуют сигнатуру MZ) и расширение .com ему присваивают по традиции.
Прежде чем приводить пример .com-программы, познакомимся с новой для нас директивой Ассемблера.
ORG выражение — вычисляет выражение и присваивает полученное значение счетчику адреса. (Напомню, что счетчик адреса хранится во встроенной переменной $.)
Пример программы повторяет нашу первую программу, но в новом формате:

pc.asm
INCLUDE macro.inc
.MODEL tiny
.CODE
ORG 100h ; Выделить пространство для PSP
start: jmp short begin ; "Обойти" данные
msg DB "Hello!",0Dh,0Ah,$
begin: message msg
exit
END start

Директива ORG 100h выделяет 256 байтов для префикса программного сегмента. (Можно было вместо этой директивы написать $ = $ + 100h или DB 100h DUP(?).) При написании программ в exe-формате заботиться об этом не было необходимости.
Данные рекомендуется помещать перед кодом, чтобы ассемблеру было легче сгенерировать нужные коды команд. Но по смещению 100h обязательно должна находиться команда, а не данные. Поэтому инструкция безусловного перехода передает управление на первую "настоящую" команду кода. Сегментный регистр DS теперь загружать не нужно. Программу можно было завершить не вызовом функции 4Ch, а прерыванием int 20h, специально предназначенным для завершения com-программ. (Для int 20h регистр CS должен указывать на PSP. Но для com-программ это и так выполнено.)

Последовательность действий для получения com-файла. Нужно выполнить команды:
tasm pc.asm
tlink/t pc.obj
Ключ /t указывает tlink, что нужно создавать pc.com, а не pc.exe. (t — сокращение от tiny.)
Упражнение. Какое сообщение об ошибке выдаст tlink, если вы опустите директиву ORG 100h? если вы включите директиву .STACK 100h?
Упражнение. Сравните размеры файлов p.exe (гл. ) и p.com.
С помощью MASM файл pc.com можно получить так:
ml/AT pc.asm
В ранних версиях MASM сначала нужно было получить exe-файл, а затем преобразовать его в com-файл с помощью утилиты exe2bin.

Отладка com-файлов. Если, как раньше, добавить к tasm и tlink ключи /zi и /v, то, к нашему разочарованию, td сообщит нам: "Program has no symbol table". Действуем по-другому: сначала получим pc.exe, а затем преобразуем его в pc.com, размещая отладочную информацию в отдельном файле.
tasm/zi pc.asm
tlink/v pc.obj (создает pc.exe, выдает предупреждение: No stack — игнорируем его)
tdstrip –s –c pc.exe
td pc.com
Утилита tdstrip переносит отладочную информацию из pc.exe в файл pc.tds (ключ –s), и преобразует pc.exe в pc.com (ключ –c). Отладчик, не найдя в исполняемом файле отладочной информации, ищет ее в файле с расширением tds.