5.10.2. 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.


Microsoft (R) Macro Assembler Version 5.00                1/1/80 04:00:28
Фиг. 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:00:28
Фиг. 5.14 Подпрограмма вывода                                   Page  1-1
PAGE ,132

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 говорят о том, что это область памяти предназначена для стека. Редактор связей, кроме того, проверяет, правильно ли установлен указатель стека в момент, когда управление передается программе.