Для программ, будь они подпрограммами или функциями, принима-
ющими входные параметры, должна быть разрешена проблема передачи
им параметров. При программировании на языке высокого уровня в
этом отношении программист обычно не имеет выбора. В языке Ас-
семблер таких опций много. Мы рассмотрим все опции, хотя исполь-
зовать некоторые из них необходимо с осторожностью.
Передача через регистры
Наиболее общим способом передачи данных при программировании
на языке Ассемблер является способ передачи данных через регист-
ры. Немедленная доступность и высокая скорость делают их основны-
ми средствами для решения поставленной задачи. Регистры всегда
отделены от кода операции, независимо от используемой программной
среды. Почти все вызываемые функции MS-DOS передают свои данные
таким способом. Короткие программы языка Ассемблер, являющиеся
интерфейсными программами с MS-DOS, для манипулирования данными
часто используют те же самые регистры, которые требуют функции,
вызывающие эти данные MS-DOS.Это приводит к ощущению создания па-
- 2-8 -
раметра в том же регистре, что и MS-DOS.
Одной из трудностей, возникающей при использовании этого спо-
соба, является то, что количество регистров, имеющееся в наличии,
ограничено. Если имеется программа, требующая большее количество
регистров, чем имеется в наличии, то это вызывает лишние хлопоты.
Новые микропроцессоры имеют меньшие ограничения, чем старые, но
количество регистров все равно ограничено. Кроме того, если необ-
ходимо переместить часть программы из одного типа процессора в
другой, то ситуация, в которой два процессора могут совместно ис-
пользовать один и тот же набор регистров, маловероятна. Вам при-
дется перепроектировать все интерфейсы модулей.
Другой трудностью является то, что необходимо непрерывно сох-
ранять используемую дорожку, на которую выводится каждый регистр.
Эта игра "кто первый" может наскучить даже наиболее опытному иг-
року. Особое расстройство вызывает случай, когда принято решение,
что регистр X освободился и, следовательно, освободилась програм-
ма модуля. Позднее, когда принимается решение об использовании
того же самого модуля в другом месте, может оказаться, что не ос-
вободился как раз только регистр X. Так команда PUSH (запомнить
содержимое регистра в стеке) записывает в стек значение, содержа-
щееся в регистре X, затем выполняется вызов и команда POP (выб-
рать значение регистра из стека) выбирает из стека значение ре-
гистра и заносит его в регистр X. Таким образом, в результате та-
кого оборота X содержит возвращаемое значение. Видите, что теперь
освободилось? И так случается очень часто.
Практически, ограничением передачи параметров через регистры
является ограничение объема информации, передаваемой через ре-
гистры, до 16 бит, т.е. размера наибольшего регистра. В связи с
тем, что большинство переменных имеют тенденцию использоваться в
виде байтов или слов, ограниченный размер регистра не является
серьезной проблемой. Когда передаваемые данные превышают размер
регистра, вместо данных вызывающая программа может передать их
адрес в памяти. Конечно, для правильного использования данных вы-
зывающая программа должна знать, какой был указан тип данных. При
вызове функций MS-DOS всякий раз, когда они требуют большого ко-
личества данных, используется этот механизм передачи данных, с
заключающимся в указании адреса данных в памяти.
Передача данных через общую область памяти
Следующим способом передачи данных является использование за-
ранее подготовленной области памяти. Понятие "заранее подготов-
ленная" используется в том смысле, что и вызывающая и вызываемая
программы "договорились" между собой о том, что их данные переда-
ются в некоторую область основной памяти. Программа А знает, что
вывела квитанции об оплате за последний месяц в область памяти,
помеченную как FOO, и программа В знает как выглядит эта информа-
ция в области памяти FOO. Поэтому область памяти FOO называется
общей областью памяти.
Передача данных через общую область памяти имеет, по крайней
мере, один существенный момент. В пределах физических возможнос-
тей используемого компьютера можно передать столько данных,
сколько необходимо. Передача данных через общую область памяти
позволяет передавать и принимать целую гамму свободных регистров
и допускает передачу данных любого размера от буфера в один байт
до нескольких килобайт.
Кроме того, передача данных через общую область памяти делает
- 2-9 -
доступными передаваемые данные любому модулю, которому они требу-
ются. Это большое преимущество, т.к. запрашиваемые данные переда-
ются от модуля верхнего уровня через большое количество внутрен-
них модулей к модулю нижнего уровня. Поэтому, каждый модуль не
должен обрабатывать те данные, которые он не использует.
Отрицательной стороной этого способа передачи данных является
то, что зависимость от общей памяти может ограничить общность и
повторность использования модулей. Рассмотрим ряд модулей, пред-
назначенных для чтения и записи файлов. Если модули кодируются
для использования общего блока памяти, то может возникнуть проб-
лема одновременного открытия двух файлов. Если программа была
спроектирована для выполнения сравнения, то она должна скопиро-
вать один набор данных из буфера в область памяти для предотвра-
щения возможной перезаписи буфера.
Последний недостаток способа передачи данных через общую об-
ласть памяти вытекает из его особенности. В связи с тем, что об-
ласть памяти доступна любому модулю, это типичный случай "игры
по правилам". Защита данных от случайного разрушения почти невоз-
можна. Обычно это не является большим риском (поскольку ошибки
программы общие), но становится очень существенным фактором при
рассмотрении повторно используемого программирования (рассматрива-
емого в следующем разделе "Типы кодирования").
Передача данных через память программы
Передача данных через память программы является одним из ва-
риантов передачи данных через общую область памяти. Первым отли-
чием является то, что данные располагаются в памяти программы
(программном сегменте), а вторым - то, что местоположение данных
определяется с помощью инструкции CALL, поскольку данные размеща-
ются непосредственно после вызова.
Вызываемая программа выбирает адрес возврата из стека, ис-
пользуемого в качестве указателя на область памяти, складывает
размер области памяти с адресом возврата и помещает его обратно в
стек. При возврате управления в вызывающую программу адрес возв-
рата располагается сразу же после области данных.
Это кажется удобным до тех пор, пока не примем во внимание,
что микропроцессор 8086 специально спроектирован для разделения
программы и областей данных. Передача данных через память прог-
раммы требует, чтобы программный сегмент и сегмент данных были
установлены в одно и то же значение, поэтому адрес возврата отно-
сится к программному сегменту.
Самой наихудшей проблемой способа передачи данных через па-
мять программы является то, что он требует манипуляции стека, в
котором происходит то самое "сближение" для текущей самомодифици-
руемой программы. Одно правило, которое всегда необходимо пом-
нить, состоит в том, чтобы никогда, никогда не модифицировать па-
мять программы! Если все же поддаться соблазну, то программа
станет почти неподдающейся отладке без тщательного анализа логики
программного обеспечения.
Передача данных в стек
Передача данных в стек является способом, используемым боль-
шинством компиляторов языков высокого уровня для реализации вызо-
ва процедур. При этом способе передачи данных перед выполнением
- 2-10 -
вызова все требуемые параметры заносятся в стек. После вызова вы-
зывающая программа осуществляет доступ к данным без их пересылки.
Проектировщики семейства микропроцессоров 8086 поддержали этот
способ при обеспечении регистра BP (base pointer - указатель ба-
зы). Регистр BP имеет удивительную особенность адресации его опе-
рандов относительно сегмента стека. Это означает, что при уста-
новке значения регистра BP в правильное положение, содержимое
стека может быть адресовано путем использования индексной адреса-
ции.
Что же такое "правильное положение" при загрузке в BP? Это не
сам SP (stack pointer - указатель стека), поскольку SP указывает
на адрес возврата в стек. Данные обычно начинаются с ячейки SP+2
или ячейки SP+4. Почему плюс 2, или плюс 4? Потому, что для вызо-
ва процедуры near (близкий) процессор запоминает только текущее
смещение (указатель инструкции) в стеке (2 байта), в то время как
для вызова процедуры far (далекий) процессор запоминает смещение
и сегмент программы в стеке (4 байта). Вызываемая программа может
быть закодирована для начала доступа в правильном положении (в
зависимости от типа программы) при использовании следующей адре-
сации:
NEAR FAR
mov bp,sp mov bp,sp
mov <1-й аргумент>,[bp+2]
mov <1-й аргумент>,[bp+4]
... ...
Заметим, что если необходимо сохранить содержимое регистра
BP, то обычно в этом случае вызываемая программа должна поместить
BP в стек, изменив адрес первого параметра на [BP+4] для програм-
мы near и на [BP+6] для программы far. Чтобы избежать это измене-
ние адресов, необходимо перед тем, как поместить параметры в
стек, передать вызывающей программе ответственность за сохранение
BP. Однако, из-за причин обеспечения совместимости это не реко-
мендуется. Вместо этого более предпочтительным способом передачи
параметров является структура, показанная в листинге 2-1. Исполь-
зование этой структуры, заимствованной большинством языков высо-
кого уровня, поможет при разработке мобильных, многократно ис-
пользуемых программ. Эти программы могут быть собраны в
"инструментальный набор", который необходимо использовать во мно-
гих местах для облегчения программирования и повышения производи-
тельности работы.
При возврате вызываемой программы параметры, которые были по-