 |
ется значительно меньшей гибкостью, чем передача их через регист-
ры. Если вы решили использовать передачу параметров через стек,
вы вероятно будете использовать соглашения, принятые в предпочи-
таемом вами языке высокого уровня. Это позволит легко компоновать
подпрограммы на Ассемблере с программами, написанными на данном
языке. В соответствующих главах и приложениях данного руководства
приводится полное описание соглашений по передаче параметров в
Турбо Си, Турбо Паскале, Турбо Бейсике и Турбо Прологе, и приве-
дены примеры на Ассемблере.
Возвращаемые значения
-----------------------------------------------------------------
Подпрограммы часто возвращают значения в вызывающую програм-
му. В программах на Ассемблере, которые предполагается вызывать
из программы на языке высокого уровня, для возврата значений вы
должны следовать соглашениям данного языка. Например, функции,
вызываемые в языке Си, должны возвращать 8- или 16-битовые значе-
ния (значения символьного, целого типа и ближние указатели) в ре-
гистре AX, а 32-битовые значения (длинные целые и дальние указа-
тели) - в паре регистров DX:AX. В главах 6 - 9 данного руководст-
ва дается подробное описание соглашений по возвращаемым значениям
языка Турбо Си, Турбо Паскаля, Турбо Бейсика и Турбо Пролога.
В программах, где используется только язык Ассемблера, в от-
ношении возвращаемых значений допускается полная свобода: вы по-
жете помещать их в тот регистр, какой захотите. Фактически, в ре-
гистре флагов подпрограммы могут даже возвращать информацию о
состоянии (в виде установки флага переноса или флага нуля). Одна-
ко, лучше установить некоторые соглашения и их придерживаться.
Полезным соглашением может служить возврат 8-битовых значений в
регистре AL и 16-битовых значений в регистре AX.
Основная проблема при использовании в Ассемблере возвращае-
мых подпрограммами значений состоит в том, что при возврате
информации подпрограммы могут разрушить важную для вызывающей
программы информацию. В Ассемблере легче писать обращение к
подпрограмме, не помня о том, что подпрограмма возвращает
значение, скажем в SI (или что данная подпрограмма просто
изменяет SI), но при этом вы получите программу, в которой трудно
будет выявлять ошибки.
По этой причине лучше сводить к минимуму число значений,
возвращаемых в регистрах (лучше всего до одного) и возвращать до-
полнительные значения, сохраняя их в ячейках памяти, на которые
ссылаются передаваемые указатели (как это делается в Паскале и
Си).
Сохранение регистров
-----------------------------------------------------------------
Соответствующее сохранение регистров при обращении к под-
программе - это существенный момент в программировании на Ассем-
блере. В современных языках высокого уровня подпрограмма обычно
не может изменить значения переменных вызывающей программы, если
вызывающая программа этого явным образом не допускает. В Ассем-
блере это не так: переменные вызывающей программы хранятся часто
в тех же регистрах, что и регистры, используемые подпрограммой.
Например, если подпрограмма изменяет регистр, значение которого
вызывающая программа установила перед вызовом подпрограммы, но
который она использует после обращения к ней, вы получите ошибку.
Одно из решений этой проблемы состоит в том, чтобы при входе
в каждую подпрограмму заносить в стек все используемые ей регист-
ры и восстанавливать их из стека перед возвратом в вызывающую
программу. К сожалению, для этого требуется существенное время и
большой объем кода. Другая возможность заключается в том, чтобы
ввести правило, что вызывающая программа никогда не рассчитывает
на сохранение регистров подпрограммой и все функции по их сохра-
нению выполняет сама. Но это малопривлекательно, поскольку су-
щественным доводом в пользу применения Ассемблера является свобо-
да эффективного использования регистров.
Если говорить кратко, то в языке Ассемблера существует ко-
нфликт между скоростью и простотой программирования. Если вы со-
бираетесь использовать Ассемблер, вы сможете писать быстрые и
компактные программы, а это означает, что нужно разумно относить-
ся к сохранению регистров и обеспечить, чтобы при вызове каждой
подпрограммы из-за регистров не возникало конфликтов. Наилучшим
подходом является аккуратное комментирование каждой подпрограммы
и указание, какие регистры она использует, и обращение к данным
комментариями при каждом вызове подпрограммы.
Нужно уделять внимание как отслеживанию сохранения регист-
ров, так и максимально эффективному их использованию. В програм-
мировании на Ассемблере это одинаково важно. Языки высокого уров-
ня выполняют за вас эту работу, но они, как мы уже упоминали, не
позволяют получать такие быстрые и компактные программы, какие
можно получить с помощью Ассемблера.
Пример программы на языке Ассемблера
-----------------------------------------------------------------
Давайте теперь попробуем реализовать все то, что мы узнали в
последних двух главах, в виде полезного примера программы на Ас-
семблере. Эта программа WCOUNT.ASM подсчитывает число слов в фай-
ле и выводит его на экран.
;
; Программа для подсчета числа слов в файле. Слова разделены
; пробелами, символами табуляции, возврата каретки или перевода
; строки.
;
; Вызов: wc < имя_файла.расш
;
DOSSEG ; выбрать стандартный
; порядок сегментов
.MODEL SMALL ; код и данные
; помещаются в 64К
.STACK 200h ; стек размером 512
; байт
.DATA
Count DW 0 ; используется для
; подсчета слов
InWhitespace DB ? ; устанавливается в
; значение 1, когда
; последним прочитанным
; символов является
; разделитель
TempChar DB ? ; временная память,
; используемая
; в GetNextCharacter
Result DB 'Число слов: ', 5 DUP (?)
; строка, используемая
; для вывода результата
CountInsertEnd LABEL BYTE ; используется для
; определения конца
; области, в которой
; хранится строка со
; значением счетчика
DB 0dh,0ah,'$' ; функция DOS 9
; работает со строками,
; которые завершаются
; символом $
.CODE
ProgramStart:
mov ax,@Data
mov ds,ax ; DS указывает на
; сегмент данных
mov [InWhitespace],1 ; предположим, это
; разделитель, так как
; первый отличный от
; разделителя символ,
; который мы найдем,
; будет отмечать начало
; слова
CountLoop:
call GetNextCharacter ; получить следующий
; символ для проверки,
jz CountDone ; если он имеется
call IsCharacterWhitespace ; это разделитель?
jz IsWhitespace ; да
cmp [InWhitespace],0 ; символ не является
; разделителем - теперь
; мы в разделителе?
jz CountLoop ; мы не в разделителе
; и символ не является
; разделителем, поэтому
; с этим символом работа
; окончена
inc [Count] ; мы в разделителе и
; символ не является
; разделителем, значит
; мы нашли начало нового
; слова
mov [InWhitespace],0 ; отметить, что мы боль-
; ше не в разделителе
jmp CountLoop ; обработать следующий
; символ
IsWhitespace:
mov [InWhitespace],1 ; отметить, что мы в
; разделителе
jmp CountLoop ; обработать следующий
; символ
;
; Подсчет завершен - вывести результаты
;
CountDone:
mov ax,[Count] ; число, которое нужно
; преобразовать в строку
mov bx,OFFSET CountInsertEnd-1 ; ссылка на
; конец строки, в
; которую нужно
; поместить число
mov cx,5 ; число цифр, которые
; нужно преобразовать
call ConvertNumberToString ; преобразовать
; число в строку
mov bx,OFFSET Result ; ссылка на строку
; результата
call PrintString ; вывести результат
mov ah,4ch ; функция DOS
; завершения программы
int 21h ; завершить программу
;
; Подпрограмма получения следующего символа из стандартного
|