-----------------------------------------------------------------
- 2-31 -
Символ lbase в листинге 2-9 определен как базовый адрес для
доступа ко всем локальным переменным. Действительной ссылкой,
создаваемой в инструкции MOV, является:
mov [BP - ??tsize].my_var,10
Символ ??tsize устанавливается реализацией макроса инструкции
ENTER в количества байтов, добавляемых в стек по инструкции
ENTER, не включая значение регистра BP. Значение этого символа
вычисляется как local + level * 2. При вычитании значения символа
??tsize из содержимого регистра BP, результатом является адрес
верхней части стека. Таким образом, все ссылки структуры имеют
положительное смещение от символа lbase. Даже если используется
версия машинного кода инструкции ENTER, можно легко написать мак-
рос, который вычисляет значение символа ??tsize и создает инс-
трукцию ENTER таким образом, что этот механизм может быть с успе-
хом использован на процессорах 186/188/286.
Другим символом, определенным в листинге 2-9, является символ
pbase - базовый адрес для доступа ко всем переменным, переданным
в стек. Значение символа pbase равно [BP + 4], чтобы охватить 2
байта, помещенные в стек как часть near (близкий) инструкции CALL
(вызвать процедуру) и 2 байта, требующиеся для регистра BP, поме-
щенного в стек по инструкции ENTER. После того, как определена
структура параметров стека, символ pbase можно использовать по
имени его поля для символического доступа к данным, например,
pbase.my_param.
Получив описание простого использования инструкции ENTER, вер-
немся к вопросу об указателях блока данных. Что это такое? Каждый
указатель блока данных указывает на начало блоков данных предыду-
щей программы стека. Путем загрузки регистра BP содержимым одного
из указателей блока данных, размещенного в текущем блоке, может
быть получен доступ к локальным переменным предыдущего уровня.
Первоначально это было спроектировано для реализации языков прог-
раммирования высокого уровня таких как, например, Паскаль, где
программа имеет автоматический доступ к переменным порождающей
программы. Если читатель недостаточно созрел в отношении высокого
уровня структурного программирования на языке Ассемблер, то он ве-
роятно пропустит использование возможностей указателя блока
инструкции ENTER. Во всяком случае, если читатель решил испробо-
вать использование инструкции ENTER с указателем блока, то незна-
чительные эксперименты дадут ему возможность прочувствовать ее
функционирование.
Краткое изложение размещения программного кода
Заметим, что повторная входимость не является необходимым
условием переместимости программ, и что переместимость не является
необходимым условием повторной входимости программ. "Настройка
программ" (путем модификации адресов при размещении программы по
определенному адресу) применяется для возможности перемещения
программы в памяти. "Повторная входимость" применяется для прог-
рамм, имеющих "безопасную" локальную память. "Рекурсивные" прог-
раммы - это тип повторно-входимых программ с ослабленными ограни-
чениями, когда программист знает в какой точке должны быть сохра-
нены данные при подготовке следующего вызова.
Кроме того, при написании повторно-входимых программ нельзя
забывать о том, что параметры программы также должны быть повторно
-входимыми. Когда новая процедура или задача получает управление,
данные должны передаваться в такую область вызываемой программы,
которая либо всегда защищена (как стек), либо всегда сохраняемая
(например, все программы обслуживания прерываний сохраняют свои
регистры при вызове программы).
Также следует помнить о том, что имеется два типа переместимо-
го программного кода. К первому типу относится настраиваемая сис-
тема программ операционной системы MS-DOS, когда MS-DOS, используя
схему настройки, изменяет значения переменных сегмента для того,
чтобы настроить программу. Ко второму типу относятся самонастраи-
вающиеся программы, для которых не требуется схема настройки. Са-
монастраивающимися программами могут быть только такие программы,
которые имеют только адресацию смещения в инструкциях CALL (вы-
звать процедуру) и JMP (безусловный переход).
Интерфейс с языками высокого уровня
Наиболее общим использованием языка Ассемблер в настоящее вре-
мя является применение его в качестве приложения к языку програм-
мирования высокого уровня. При разработке программы, как правило,
обычно используют язык высокого уровня и лишь небольшую часть мо-
дулей пишут на языке Ассемблер. Язык Ассемблер используется тогда,
когда критичны скорость работы программы или ее размер, или когда
язык высокого уровня не обеспечивает доступ к полным возможностям
или к аппаратным средствам.
Имеется три главных области, относящихся к связи программ на
языке Ассемблер с программами на языке высокого уровня. Это - со-
гласование имен между двумя модулями; обработка любых специальных
установок, которые могут требовать язык программирования и компи-
лятор языка; настройка модулей языка Ассемблер для надлежащей по-
следовательности вызова и инструмента передачи параметров, исполь-
зуемых компилятором конкретного языка высокого уровня.
В прошлом, для языков высокого уровня было достаточно мало
правил регулировки соглашений о присвоении имен и последователь-
ностей вызова. Сегодня ситуация во многом изменилась, т.к. многие
компиляторы следуют стандартам Американского национального инсти-
тута стандартов (ANSI). В связи с широким использованием компиля-
торов языков высокого уровня фирмы "Майкрософт" и в связи с тем,
что они придерживаются стандартов ANSI, оказалось возможным выб-
рать компиляторы фирмы "Майкрософт" с языков программирования:
Бэйсик, Си, Фортран и Паскаль для иллюстрации соглашений вызова
подпрограмм.
Соглашения о связях для языка Си фирмы "Майкрософт"
Соглашения о связях, проиллюстрированные в листинге 2-8,
представляют типичную программу на языке программирования Си. Если
бы программу Example перетранслировать на язык Си, то ее начальные
предложения были бы похожи на следующие:
void Example (Param1, Param2, Param3)
int Param1, Param2, Param3 ;
{
int LocIndx ;
char LocChar [14] ;
int LocWord ;
... ...
- 2-33 -
В языке Си все подпрограммы являются также функциями; любая
подпрограмма может возвращать значение в вызывающую программу. В
связи с тем, что приведенная функция не возвращает значение, она
объявлена как пустая функция (void).
Язык программирования Си обеспечивает использование автомати-
ческих (automatic) переменных для запоминания локальных данных.
Заметим, однако, что отсутствует стандарт, предписывающий порядок
размещения в стеке локальных переменных.
В листинге 2-8, рис. 2-1 и в приведенном выше фрагменте прог-
раммы показано как язык Си помещает свои аргументы в порядке, об-
ратном их объявлению. Целью этого способа является то, что если
передается переменное количество параметров, то вызываемая прог-
рамма может всегда найти самый левый параметр на фиксированной
позиции в стеке. Параметр Param1 будет всегда размещаться в
[BP + 4], независимо от того, какое количество параметров было в
действительности передано. Программы на языке Си, допускающие ис-
пользование этой особенности, обычно используют самый левый или
первый параметр для передачи общего количества параметров, пере-
даваемых в вызываемую программу. При этом способе вызываемая
программа может определить сколько параметров ей необходимо про-
читать.
Другой особенностью, отмечаемой в языке Си, является то, что
параметры почти всегда передаются по значению. Если вызвать про-
цедуру Example (пример) с переменной FOO, то содержимое перемен-
ной FOO будет помещено в стек. Вызываемая программа, таким обра-
зом, работает с копией переданной переменной, а не с ней самой.
Исключение этого способа состоит в том, что массив обычно переда-
ется по адресу. (В стандартном языке Си идентификатором массива
является его адрес, так что это кажущееся исключение в действи-
тельности согласуется с синтаксисом языка Си). Однако, язык Си
позволяет программисту передавать адрес любой переменной, если
потребуется.
Компилятор языка Си фирмы "Майкрософт" поддерживает богатейшую
среду программирования, позволяющую опытному программисту полное
управление памятью, используемой модулем. В приведенном примере
представлена среда программирования языка Си по умолчанию, состо-
ящая из вызова программы near (близкий) и ссылки near на данные.
Вопреки уже сделанным попыткам, не будем заменять версию про-
цедуры Example (пример) программой на языке Си. Одно из препятс-
твий, которое необходимо преодолеть, состоит в согласовании имен,
используемых между вызывающей программой на языке Си и вызываемой
программой на языке Ассемблер. Проблема заключается в том, что
компилятор языка Си ставит префикс "подчеркивание" (_) перед все-
ми именами. Когда компилятор генерирует вызов программы Example
(пример), он реально предполагает, что именем программы пункта
назначения является "_Example". Возможно, что эта терминология
разработана для предотвращения коллизий между пространством имени
компилятора и пространством имени Ассемблера. Если и вызывающая и
вызываемая программы написаны на языке Си, то компилятор трансли-
рует обе ссылки и поэтому нет никаких неприятностей. Когда одна
из ссылок на языке Ассемблер, то необходимо выполнить трансляцию
самим. Эта трансляция применяется для выдачи имен переменных гло-
бальных данных также успешно, как и программных меток.
- 2-34 -
Следует отметить два существенных момента:
- в языке программирования Си имена ограничены 8 символами;
- все имена в языке программирования Си чувствительны к ре-
гистру.
В языке программирования Си "Example" и "example" это два
разных имени. Программы на языке программирования Ассемблер долж-
ны ассемблироваться с переключателем /mx для предохранения от ис-
пользуемого регистра любого имени.