EXTRN и PUBLIC
EXTRN и PUBLIC
Редактор связей не может, однако, выполнить все, о чем говорилось
выше, самостоятельно. Ассемблер должен получить от программиста
информацию о подпрограммах, относящихся к другому программному
модулю. Это ввполняется с помощью оператора PUBLIC, извещающего
ассемблер о том, что данное символическое имя доступно другим
программам. Кроме того, программист указывает ассмеблеру, какие из
символических имен является внешними для данного программного
модуля. В языке ассемблера это реализуется оператором EXTRN,
который объявляет соответствующее имя внешним для текущего
ассемблирования, чтобы оно могло быть правильно обработано.
Ассемблер помечает данную команду таким образом, чтобы редактор
связей мог впоследствии найти ее и вставить туда правильное
значение адреса.
Оператор EXTRN выполняет две фуекции. Во-первых, он сообщает
ассемблеру, что указанное символическое имя является внешним для
текущего ассемблирования. Конечно, на этом этапе ассемблер мог бы
считать, что любое имя, не идентифицированное им в процессе
ассемблирования, является внешним. Однако, если когда-нибудь вы
ошиблись в указании имен, то ассемблер решит, что имеется в виду
внешнее имя, и не выдает сообщения об ошибке. Это отложит индикацию
ошибки до этапа редактирования связей. Для большинства
программистов это слишком поздно, особенно, если речь идет о чем-то
простом, вроде описки. Таким образом, ассемблер индицирует ошибку в
случае любого не определенного им символического имени.
Вторая функция оператора EXTRN состоит в том, что он указывает
ассемблеру тип соответствующего символического имени. Так как
ассемблирование является очень формальной процедурой, то ассемблер
должен знать, что представляет из себя каждый символ. Это позволяет
ему генерировать правильные команды. В случае данных оператор EXTRN
может указывать на байт, двойное слово или другой типовой элемент.
Тип имени подпрограммы или другой программной метки может быть либо
NEAR, либо FAR, в зависимости от того, в каком сегменте она
находится. От программиста требуется указать в операторе EXTRN тип
символического имени. Так как кроме того ассемблером осуществляется
посегментная адресация программы, то оператор EXTERN указывает на
сегмент, в котором появляется данный идентификатор. Это не входит в
синтаксис оператора EXTRN, а определяется местоположением этого
оператора в программе. Ассемблер считает, что внешнее имя относится
к тому же сегменту, в котором появляется оператор EXTERN для этого
символического имени.
На Фиг. 5.13 приведен пример ассемблерной программы,
иллюстрирующей использование оператора EXTRN. Здесь имеются два
имени, являющиеся внешними для данной программы. OUTPUT_CHARACTER
обозначает однобайтовую переменную. Соответствующий этой переменной
атрибут ":BYTE" указывается после имени переменной. Указатель NEAR
программной метки OUTPUT_ROUTINE говорит о том, что она находится в
том же сегменте. Хотя приведенная на Фиг. 5.13 прогграмма содержит
ссылки на эти символические имена, при трансляции ассемблер знает,
как ему сегментировать правильные команды. Если бы оператор EXTRN
отсутствовал в программе, то в этом случае ассемблер
инициализировал бы ошибки. Из ассемблерного листинга видно, что
после поля адреса в командах, ссылающихся на внешние имена, стоит
символ E.
icrosoft (R) Macro Assembler Version 5.00 4/2/89 16:06:36
Фиг. 5.13 Основная программа Page 1-1
PAGE ,132
TITLE Фиг. 5.13 Основная программа
0000 STACK SEGMENT STACK
0000 0040[ DW 64 DUP (?) ; Резервирование места для стека
????
]
0080 STACK ENDS
0000 CODE SEGMENT PUBLIC
EXTRN OUTPUT_ROUTINE:NEAR, OUTPUT_CHARACTER:BYTE
ASSUME CS:CODE
0000 START PROC FAR
0000 1E PUSH DS ; Сегмент адреса возврата
0001 B8 0000 MOV AX, 0
0004 50 PUSH AX ; Смещение адреса возврата
0005 FC CLD ; Установка направления
0006 8C C8 MOV AX, CS ; Установка сегментного регистра
0008 8E D8 MOV DS, AX
ASSUME DS:CODE ; Индикация состояния регистра
000A 8D 36 001D R LEA SI, MESSAGE ; Адрес строки сообщения
000E CLOOP:
000E AC LODSB ; Выборка следующего байта сообщения
000F A2 0000 E MOV OUTPUT_CHARACTER, AL ; Сохранение в памяти символа
0012 E8 0000 E CALL OUTPUT_ROUTINE ; Вывод символа
0015 80 3E 0000 E 0A CMP OUTPUT_CHARACTER, 10 ; Проверка на символ конца сообщения
001A 75 F2 JNE CLOOP ; Обработка следующего символа
001C CB RET ; Возврат в ДОС
001D 9D E2 A0 20 AF E0 AE MESSAGE DB 'Эта программа - тест', 13, 10
A3 E0 A0 AC AC A0 20
2D 20 E2 A5 E1 E2 0D
0A
0033 START ENDP
0033 CODE ENDS
END START
Фиг. 5.13 Главная процедура
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:02:28
Фиг. 5.14 Подпрограмма вывода Page 1-1
PAGE ,132
TITLE Фиг. 5.14 Подпрограмма вывода
0000 CODE SEGMENT PUBLIC
ASSUME CS:CODE,DS:CODE ; Это должно быть так при вызове
PUBLIC OUTPUT_CHARACTER, OUTPUT_ROUTINE
0000 ?? OUTPUT_CHARACTER DB ?
0001 OUTPUT_ROUTINE PROC NEAR
0001 A0 0000 R MOV AL, OUTPUT_CHARACTER ; Выборка выводимого символа
0004 B4 0E MOV AH, 14 ; Функция вывода в BIOS
0006 BB 0000 MOV BX, 0 ; Установка номера страницы
0009 BA 0000 MOV DX, 0
000C CD 10 INT 10H ; Вызов подпрограммы вывода на экран
000E C3 RET ; Возврат в вызывающую программу
000F OUTPUT_ROUTINE ENDP
000F CODE ENDS
END
Фиг. 5.14 Процедура вывода
Рассмотрим эту же задачу с другой стороны. Каким образом
редактор связей узнает о местоположении внешних имен? На Фиг. 5.14
приведена подпрограмма, на которую ссылается другая программа,
относящаяся к Фиг. 5.13. Переменные и программные метки, на которые
имеются ссылки в программе, на Фиг. 5.13, объявлены в подпрограмме
с помощью оператора PUBLIC. Это означает, что их имена доступны для
другого программного модуля. Ни на какие другие переменные или
программные метки в этой программе, не указанные в операторе
PUBLIC, ссылки в других программах невозможны. Хотя это может
показаться неудобным, однако, если все имена имели бы атрибут
PUBLIC, то возникла бы другая трудность. Это означало бы, что
каждое имя в любом из модулей, которые вы могли бы связать между
собой, должны быть уникальными, т.е. вы никогда бы не смогли
использовать одно и то же символическое имя дважды в разных
модулях. Это может быть серьезным препятствием для повторного
использования некоторых подпрограмм, так как такое использование
возможно и через несколько лет, а помнить все символические имена и
следить за тем, чтобы ни одно из них не повторялось дважды довольно
сложно. Заметьте, что в операторе PUBLIC не требуется указывать
атрибуты имен: об этом заботятся обычные операторы языка
ассемблера.
Программа LINK устанавливает соответствие между всеми внешними
именами и соответствующими операторами PUBLIC, которые их
объявляют. После этого редактор связей записывает правильные
значения адресов в команды, гдк есть ссылки на внешние имена.
Обрабатываются те поля в командах, рядом с которыми в ассемблерном
листинге стоял символ "E".
Кроме того, ассемблер осуществляет объединение любых сегментов
с одними тем же именем. В случае программ на Фиг. 5.13 и П5.14
основная программа и подпрограмма принадлежат одному и тому же
сегменту с именем CODE. Так как в операторе EXTRN основной
программы для программы OUTPUT_ROUTINE указан атрибут NEAR, то
желательно, чтобы эта программа была в том же сегменте. Атрибут
PUBLIC в операторе SEGMENT указывает редактору связей объединить
оба программных модуля в один выполняемый сегмент.
В программе на Фиг. 5.13 есть еще один сегмент, который следует
рассмотреть. Данная программа выполняется как программа типа .EXE.
При передаче управления программе типа .EXE система DOS организует
для этой программы стек. Информация для стека поступает от
редактора связей, который записывает ее в головную метку файла типа
.EXE. Подготовить все для стека обязан программист. Если он этого
не сделает, то редактор связей выдает соответствующее сообщение. В
обычной ситуации это не может служить препятствием для выполнения
программы. Однако в таком случае параметры стека для программы
выбираются по умолчанию, т.е. местоположение и размер стека могут
оказаться неподходящими. За подготовку стека отвечает сегмент
STACK, входящий в программу на Фиг.5.13. Его имя STACK и задание
соответствующего атрибута равным STACK говорят о том, что это
область памяти предназначена для стека. Редактор связей, кроме
того, проверяет, правильно ли установлен указатель стека в момент,
когда управление передается программе.