- •Модульное программирование
- •Технологии программирования
- •Структурное программирование
- •Концепция модульного программирования
- •Процедуры в языке асемблера
- •Организация интерфейса с процедурой
- •Передача аргументов через регистры
- •Передача аргументов через общую область памяти
- •Передача аргументов через стек
- •Использование директив extrn и public
- •Возврат результата из процедуры
- •Связь ассемблера с языками высокого уровня
- •Связь Pascal — асемблер
- •Связь с—асемблер
- •Подведем некоторые итоги:
Возврат результата из процедуры
В отличие от языков высокого уровня, в языке ассемблера нет отдельных понятий для процедуры и функции. Организация возврата результата из процедуры полностью ложится на программиста. Как это сделать, наверное, уже понятно, ибо получение результата тоже можно рассматривать как передачу аргументов. Поэтому мы ограничимся кратким пояснением.
В общем случае программист располагает тремя вариантами возврата значений из процедуры:
С использованием регистров. Ограничения здесь те же, что и при передаче данных, — это небольшое количество доступных регистров и их фиксированный размер. Функции DOS используют именно этот способ. Из рассматриваемых здесь трех вариантов данный способ является наиболее быстрым, поэтому его есть смысл использовать для организации критичных по времени вызова процедур.
С использованием общей области памяти. Этот способ удобен при возврате большого количества данных, но требует внимательности в определении областей данных и подробного документирования для устранения неоднозначностей.
С использованием стека. Здесь, подобно передаче аргументов через стек, также нужно использовать регистр bр. При этом возможны следующие вариаты:
использование для возвращаемых аргументов тех же ячеек в стеке, которые применялись для передачи аргументов в процедуру. То есть предполагается замещение ставших ненужными входных аргументов выходными данными;
предварительное помещение в стек наряду с передаваемыми аргументами фиктивных аргументов с целью резервирования места для возвращаемого значения. При использовании этого варианта процедура, конечно же, не должна пытаться очистить стек командой ret. Эту операцию придется делать в вызывающей программе, например командой pop.
В ходе вышеприведенного обсуждения мы выяснили, что язык ассемблера не накладывает никаких ограничений на организацию процесса передачи данных и возврата значений между двумя процедурами, а в более общем случае — и
между модулями, представляющими отдельные файлы. Наиболее быстрый способ такого обмена — использование регистров. Но часто требуется связывать между собой не только программы, написанные на ассемблере, но и программы на разных языках. В этом случае универсальным становится способ обмена данными с использованием стека. Ниже мы рассмотрим, как решается проблема связи программных модулей, написанных на языке ассемблера и языках высокого уровня.
Связь ассемблера с языками высокого уровня
На протяжении всего предыдущего материала мы неоднократно подчеркивали сильные и слабые стороны языка ассемблера как языка программирования. Писать на нем достаточно объемные программы утомительно. И всегда ли это нужно? Конечно, не всегда. Если программа не предназначена для решения каких-то системных задач, требующих максимально эффективного использования ресурсов компьютера, если к ней не предъявляются сверхжесткие требования по размеру и времени работы, если вы не «фанат» ассемблера — то, на мой взгляд, следует подумать о выборе одного из языков высокого уровня. Существует и третий, компромиссный путь — комбинирование программ на языке высокого уровня с кодом на ассемблере. Такой способ обычно используют в том случае, если в вашей программе есть участки, которые либо невозможно реализовать без использования ассемблера, либо его применение может значительно повысить эффективность работы программы.
Большинство компиляторов учитывают возможность комбинирования их «родного» кода с ассемблером. Как именно? Это зависит от конкретного компилятора языка высокого уровня, поэтому нет смысла рассматривать данный вопрос абстрактно. Учитывая, что большинство программистов работают или, по крайней мере, владеют основами программирования для языков С и Pascal, дальнейшее обсуждение будет вестись для компиляторов этих языков: Borland Pascal 7.0 и Visual C++ 4.0. Но не стоит думать, что если вы используете компиляторы других фирм или других версий, то информация, приведенная ниже, вам не понадобится. Принципиальные моменты останутся теми же. Единственное, что нужно будет сделать, — это уточнить в документации некоторые опции и, возможно, особенности организации связи с кодом на ассемблере. отразим наиболее принципиальные моменты связи программ на языках Pascal и С с ассемблером.
Вначале отметим общие моменты, актуальные как для языка С, так и для Pascal. Затем на примерах конкретных программ обсудим моменты, специфичные для каждого из этих языков.
Существуют следующие формы комбинирования программ на языках высокого уровня с ассемблером:
Использование операторов типа inline и ассемблерных вставок.( Под операторами типа inline здесь понимаются средства языка высокого уровня, подобные оператору inline() языка Pascal. В скобках указывается строка машинных кодов. Для получения такой строки целесообразно написать нужный фрагмент на ассемблере, скомпилировать исполняемый модуль, а затем запустить отладчик. В окне CPU отладчика вы увидите машинные коды ваших инструкций; их нужно переписать и вставить в скобки оператора inline. При этом нужно учитывать синтаксис языка). Эта форма сильно зависит от синтаксиса языка высокого уровня и конкретного компилятора. Она предполагает, что ассемблерные коды в виде команд ассемблера или прямо в машинных командах вставляются в текст программы на языке высокого уровня. Компилятор языка распознает их как команды ассемблера (машинные коды) и без изменений включает в формируемый им объектный код. Эта форма удобна, если надо вставить небольшой фрагмент.
Использование внешних процедур и функций. Это более универсальная форма комбинирования. У нее есть ряд преимуществ:
• написание и отладку программ можно производить независимо;
• написанные подпрограммы можно использовать в других проектах;
• облегчаются модификация и сопровождение подпрограмм в течении жизненного цикла проекта. К примеру, если ваша процедура на ассемблере производит работу с некоторым внешним устройством, то при смене устройства вам нет необходимости перекраивать весь проект — достаточно заменить только работающую с ним процедуру, оставив неизменным интерфейс программы на языке высокого уровня с ассемблером.
Так как использование операторов inline и ассемблерных вставок сильно зависит от синтаксиса конкретного языка, то мы рассматривать их не будем, а уделим основное внимание связи через внешние процедуры и функции. Как вы понимаете, здесь возможны два вида связи — программа на языке высокого уровня вызывает процедуру на ассемблере и наоборот. Здесь мы также ограничимся рассмотрением связи только в одну сторону, когда программа на языке высокого уровня вызывает процедуру на ассемблере. Это наиболее часто используемый вид связи.
Вспомним синтаксис директивы ргос:
имя_процедуры PR0C [[модификатор_языка]язык] [расстояние]
Один из операндов — язык. Он служит для того, чтобы компилятор мог правильно организовать интерфейс (связь данных) между процедурой на ассемблере и программой на языке высокого уровня. Необходимость такого указания возникает вследствие того, что способы передачи аргументов при вызове процедур различны для разных языков высокого уровня.
TASM поддерживает несколько значений операнда язык. В табл. 14.1 для некоторых из этих значений приведены характерные особенности передачи аргументов и соглашения о том, какая процедура очищает стек — вызывающая или вызываемая. Под направлением передачи аргументов понимается порядок, в котором аргументы включаются в стек, по сравнению с порядком их следования в вызове процедуры. Так, к примеру, для языка Pascal характерен прямой порядок включения аргументов в стек: первым в стек записывается первый передаваемый аргумент из оператора вызова процедуры, вторым — второй аргумент и т. д. На вершине стека после записи всех передаваемых аргументов оказывается последний аргумент.
Для языка С, наоборот, характерен обратный порядок передачи аргументов. В соответствии с ним в стек сначала включается последний аргумент из оператора вызова процедуры (или функции), затем предпоследний и т. д. В конечном итоге на вершине стека оказывается первый аргумент. Что же касается очистки стека, то понятно, что должны быть определенные договоренности об этом. В языке Pascal эту операцию всегда совершает вызываемая процедура, в языке С — вызывающая.
При разработке программы с использованием только одного языка высокого уровня об этом задумываться не имеет смысла, но если мы собираемся связывать несколько разноязыковых модулей, то эти соглашения нужно иметь в виду.
Таблица 14.1. Передача аргументов в языках высокого уровня
Вспомним действия, которые мы делали для того, чтобы настроиться на аргументы в стеке. Теперь, указывая язык, с программой на котором будет осуществляться связь, все действия по настройке стека будут производиться компилятором. При этом в текст процедуры он включит дополнительные команды входа в процедуру (пролог) и выхода из нее (эпилог). При этом код эпилога может повторяться несколько раз — перед каждой командой ret. Для значения NOLANGUAGE и по умолчанию коды пролога и эпилога не создаются.
Для пояснения последних замечаний рассмотрим на конкретных примерах организацию связей между модулями на ассемблере и модулями наиболее популярных языков высокого уровня — С и Pascal.
