АССЕМБЛЕР. Компоновщик. Загрузчик. Макрогенератор


Особые случаи на первом проходе.


Таковы в общих чертах действия ассемблера на 1-м проходе. Однако есть ряд моментов, которые осложняют его работу на этом проходе.

Напомним, что необходимость в 1-м проходе обусловлена проблемами, связанными со ссылками вперед. Но оказывается, что некоторые из этих проблем проявляются уже на 1-м проходе. Рассмотрим соответствующие случаи и то, как ассемблер реагирует на них.

Первый случай. Пусть в программе имеется такой фрагмент:

         Y DB K DB DUP(0)

         X DW Y

         K EQU 3

Когда ассемблер встретит первую из этих директив, то он еще не будет знать, что означает имя K. Конечно, по смыслу можно предположить, что K - это имя константы, но вот чему равно ее значение - предположить нельзя. А знать это значение очень важно уже на 1-м проходе, т.к. от этого зависит, на сколько надо увеличивать значение СР при обработке директивы DB, от этого зависит адрес имени X. Таким образом, не зная значения K, ассемблер не может правильно продолжить свою работу.

Что делает ассемблер? Поскольку в данной ситуации никаких разумных действий (кроме забегания по тексту вперед, которое требует массы времени) он предпринять не может, то он фиксирует ошибку "ссылка вперед здесь недопустима". Учитывая этот и другие подобные случаи, авторы ЯА ввели в язык ограничение: в константных выражениях нельзя использовать ссылки вперед.

Второй случай. Рассмотрим такой фрагмент программы:

          CALL P

       L: ...

          ...

       P PROC FAR



Здесь обращение к процедуре P встретилось раньше ее описания, и это ставит перед ассемблером следующую проблему на 1-м проходе. Если P - имя близкой процедуры, тогда машинная команда, соответствующая символьной команде CALL, займет 3 байта памяти (она имеет вид КОП ofs, где ofs

- смещение имени P), и потому ассемблер должен увеличивать СР на 3. Но если P

является именем дальней процедуры, тогда соответствующая машинная команда займет 5 байтов (она имеет вид КОП ofs seg), и потому СР должен быть увеличен на 5. Так на сколько же надо увеличивать СР - на 3 или 5? А это важно знать, от этого зависит адрес метки L и всех последующих меток.


Как видно, и здесь из- за ссылки вперед ассемблер не знает, что ему делать уже на 1-м проходе. Однако фиксировать в данной ситуации ошибку неразумно, т.к. в реальных программах такие ситуации встречаются очень часто и этих ошибок было бы слишком много. Кроме того, в данной ситуации можно сделать вполне разумное предположение относительно имени P, а именно предположить, что это имя близкой

процедуры (так чаще всего и бывает в реальных программах). Учитывая все это, ассемблер в данной ситуации не фиксирует ошибку, а делает предположение, что P - это имя близкой процедуры, и далее уже действует согласно этому предположению, т.е. считает, что данная команда CALL будет транслироваться в машинную команду близкого вызова, и потому увеличивает СР на 3. Но если затем окажется, что это предположение ошибочно (как в нашем примере), тогда ассемблер уже зафиксирует ошибку.

Третий случай. Предположим, в программе переменная X

описана в конце сегмента команд. Тогда имя X

будет использовано в команде ADD до своего описания:

         ADD X,K

          ...

       X DW Y

Встретив команду ADD, ассемблер еще не будет знать, что обозначает имя X (переменную или что-то иное), и не будет знать, в каком сегменте описано имя X, а потому не будет знать, по какому сегментному регистру должно сегментироваться это имя, надо или нет перед этой командой ставить префикс. А от всего этого зависит, сколько байтов в памяти займет соответствующая машинная команда - 6 или 7.

И здесь ассемблер не фиксирует ошибку, а предполагает, что имя X

обозначает переменную (а не константу или что-то иное) и что в данной команде не должен использоваться префикс, т.е. что переменная X будет описана в сегменте, на начало которого показывает регистр, подразумеваемый по умолчанию в данной команде. Сделав такое предположение, далее ассемблер действует уже согласно ему, а именно определяет, что эта команда в целом займет в памяти 6 байтов. И опять же, если это предположение окажется ошибочным (например, у нас имя X описано в сегменте команд и потому должно сегментироваться по регистру CS), то затем будет зафиксирована ошибка.

Таковы основные случаи, когда из-за ссылок вперед ассемблер уже на 1-м проходе не знает в точности, что ему делать. Как видно, реакция ассемблера на эти случаи может быть двоякой. Если он не может сделать никаких разумных предположений относительно ссылки вперед (как в случае с константами), то он фиксирует ошибку; при этом в ЯА вводятся соответствующие ограничения. Но если можно сделать какое-то разумное предположение относительно ссылки вперед, то ассемблер делает такое предположение и далее действует согласно ему. Отметим, что эти предположения берутся не "с потолка": из всех возможных интерпретаций ссылки вперед в качестве предположения берется вариант, который наиболее часто встречается в реальных программах. Например, процедуры чаще всего бывают близкими, и именно этот вариант выбирается в команде CALL.


Содержание раздела