close

Вход

Забыли?

вход по аккаунту

?

345.Связь разноязыковых модулей лабораторный практикум на ЭВМ

код для вставкиСкачать
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
Министерство образования и науки Российской Федерации
Федеральное агентство по образованию
Ярославский государственный университет им. П.Г. Демидова
Кафедра компьютерной безопасности и математических методов
обработки информации
Связь разноязыковых
модулей
Лабораторный практикум на ЭВМ
Методические указания
Рекомендовано
Научно-методическим советом университета
для студентов специальности Прикладная математика
и информатика
Ярославль 2006
1
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
УДК 004.5
ББК В 18
С 24
Рекомендовано
Редакционно-издательским советом университета
в качестве учебного издания. План 2006 года
Рецензент
кафедра компьютерной безопасности и математических методов
обработки информации Ярославского государственного университета
им. П.Г. Демидова
Составители:
О.В. Власова. Н.Б. Чаплыгина
С 24
Связь разноязыковых модулей : лабораторный практикум на
ЭВМ : метод. укзания / сост. О.В. Власова, Н.Б. Чаплыгина; Яросл.
гос. ун-т. – Ярославль : ЯрГУ, 2006. – 40 с.
Цель лабораторных работ по практикуму на ЭВМ – изучение
архитектуры связи программы и подпрограммы, получение навыков
компоновки программ из модулей, написанных на разных языках
программирования: языке высокого уровня С++ или Pascal и машинно-ориентированном языке Ассемблере.
Методические указания предназначены для студентов 1-го курса математического факультета, обучающихся по специальности
010200 Прикладная математика и информатика (дисциплина «Практикум на ЭВМ», блок ОПД), очной формы обучения. Методические
указания будут полезны и студентам других специальностей, интересующимся вопросами взаимосвязи разноязыковых программ.
Ил. 7
УДК 004.5
ББК В 18
© Ярославский государственный университет, 2006
© О.В. Власова, Н.Б. Чаплыгина, 2006
2
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
Введение
Модульный подход к программированию является одним из
видов структурного программирования, который наряду с объектно-ориентированным программированием широко применяется
при проектировании и составлении программ. Основа модульной
технологии состоит в разбиении задачи на более мелкие составляющие части, называемые модулями. Эти модули являются независимыми функционально и связываются между собой посредством передачи данных. Их можно создавать на разных языках программирования, связывая в единый выполняемый модуль на этапе
компоновки. При выборе языка программирования высокого уровня существует возможность некоторые модули программировать
на языке Ассемблера в целях повышения эффективности программы, уменьшения ее рабочего времени. Так, например, если в данной части программы используются многократно выполняемые
циклические фрагменты или обращение к некоторым аппаратным
средствам, которое затрудняется использованием языка высокого
уровня, то данный модуль можно реализовать на машинноориентированном языке Ассемблере.
Существуют различные способы включения ассемблерного кода в программы. Большинство современных компиляторов с языков высокого уровня имеют специальные операторы, позволяющие
прямо в тексте исходной программы делать ассемблерные вставки.
Такое средство, называемое встроенным Ассемблером, позволяет
использовать инструкции Ассемблера наравне с командами языка
высокого уровня. Это дает возможность сочетать преимущества
программирования на языке высокого уровня с эффективностью
локальных фрагментов программ. В этом состоит одно из решений
проблемы связи программ, написанных на разных языках. Его недостаток в том, что программирование вставки на Ассемблере в
большой степени зависит от синтаксиса языка высокого уровня, от
контекста программы.
Другое решение состоит в использовании внешних процедур
или функций, написанных на разных языках. Можно написать на
3
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
Ассемблере отдельную функцию и вызывать ее из программы на
языке высокого уровня, например С++ или Паскаль. Этот подход
удобен тем, что функции создаются и отлаживаются независимо
друг от друга, связываясь лишь на этапе компоновки. В этом случае функции имеют более универсальный характер и могут быть
использованы другими программами, кроме того, могут быть легко
изменены или заменены другими в случае необходимости. За раздельную компиляцию приходится платить затратами на разработку
кода: программист, работающий с Ассемблером, должен вникать
во все детали организации интерфейса между кодом С++ и кодом
Ассемблера. В то время как при использовании встроенного Ассемблера Borland C++ сам выполняет спецификацию сегментов,
передачу параметров, ссылки на переменные С++, и т.д. Отдельно
компилируемые функции Ассемблера и С++ должны все это выполнять самостоятельно.
В представляемых методических указаниях рассмотрим обе
возможности связывания частей программ, написанных на разных
языках: языках высокого уровня (С++, Паскаль) и машинноориентированном языке Ассемблере. Кроме того, приведем пример
непосредственного использования переменных из одного сегмента
данных одновременно вызываемой и вызывающей разноязыковыми функциями, т.е. пример организации глобальных (внешних)
имен в случае создания модулей на разных языках.
1. Вызов программой на языке С++
функции на Ассемблере
В интерфейсе Ассемблера и C++ есть два основных аспекта.
Во-первых, различные части кода С++ и Ассемблера должны правильно компоноваться, а функции и переменные в каждой части
кода должны быть доступны (если это необходимо) в других частях кода.
Во-вторых, код Ассемблера должен правильно работать с вызовами функций, соответствующими соглашениям языка С++, что
включает в себя доступ к передаваемым параметрам, возврат значений различного типа, передачу управления в вызываемую программу и возврат управления из нее в вызывающую программу,
4
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
соблюдение правил сохранения информации в регистрах, корректную работу со стеком.
Исходный файл
на С++
имя_файла1.cpp
Компилятор
bcc.exe
Исходный файл
на Ассемблере
имя_файла2.asm
Этап компиляции
Объектный код
имя_файла1.obj
Компилятор
tasm.exe
Объектный код
имя_файла2.obj
Этап
компоновки
tlink.exe
Исполняемый модуль
имя_файла1.exe
Рис. 1. Цикл компиляции, ассемблирования и компоновки
программы, созданной на языках С++ и Ассемблере
Си и ассемблер используют одни и те же регистры процессора.
Если в ассемблерной подпрограмме используется какой-либо регистр, то его содержимое необходимо сохранить в стеке и восстановить перед завершением функции. Если нарушить какое-либо
соглашение о связи подпрограммы с вызывающей программой, то
программа в момент выхода из подпрограммы может просто «вылететь» в неизвестном направлении.
Разберем связь модулей на конкретном примере.
Пример 1. Постановка задачи
1. Программа на языке С++ читает с клавиатуры двумерный
целочисленный массив (матрицу) и передает его ассемблерной
функции, с тем чтобы получить от нее линейный массив, составленный из максимальных по столбцам положительных элементов
5
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
исходного массива (т.е. максимальный элемент каждого столбца
записывается в выходной массив, если он является положительным, в противном случае он пропускается), и после вывода полученного массива во внешний файл заканчивает работу.
2. Вызываемая функция формирует требуемый (см. пункт 1)
выходной линейный массив и его размер и, кроме того (в учебных
целях), передает размер полученного массива и как возвращаемое
значение целого типа int.
Заголовок вызываемой функции max_as:
extern "C" int max_as(int n, int m, int a[N][N], int& k, int* p).
Входные целочисленные параметры n и m представляют
размеры исходной матрицы, параметр массивового типа а представляет исходную матрицу. Выходные параметры: целое k – число элементов в полученном линейном массиве, р – указатель на
полученный массив из максимальных элементов столбцов исходной матрицы.
Вызываемая функция объявлена внешней с помощью спецификатора extern. Спецификатор extern здесь говорит о том, что
определение функции находится в другом модуле.
В языке С++ все имена функций подвергаются компилятором
уточнению для составления таблицы уникальных имен, связывающей каждое имя функции с ее адресом. В С++ есть возможность
перегружать имена функций, давая одинаковые имена функциям,
аналогичным по выполняемым действиям и различающимся списком передаваемых параметров, в частности типом параметров. В
случае перегрузки фактически разные функции имеют одинаковые
имена, т.е. имя функции теряет уникальность, необходимую для
правильного обращения к соответствующему телу функции. Для
восстановления уникальности имен и правильного связывания с
соответствующими адресами компилятор использует алгоритм
уточнения (mangling) имен функций. Сгенерированное новое имя
зависит от спецификаций параметров функции и несет информацию об их типе. Оно используется для поиска нужной функции при
вызове. Например, если откомпилируем следующий фрагмент кода
С++ с запросом о создании ассемблерного кода:
6
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
void subrout(void){
}
void subrout(int i){
}
void subrout(float f){
},
то получим ассемблерный вариант исходного текста
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ..
@subrout$qv proc far ; void subrout(void)
ret
@subrout$qv endp
;
@subrout$qi proc far ; void subrout(int i)
push bp
mov bp,sp
pop bp
ret
@subrout$qi endp
;
@subrout$qf
proc far ; void subrout(float f)
push bp
mov bp,sp
pop bp
ret
@subrout$qf endp
public
@subrout$qf
public
@subrout$qi
public
@subrout$qv
....................................
Имена функций изменились. Для программирования на С++
знание этого факта не является необходимым и не используется
при написании программ. При использовании этих функций компилятор С++ сформирует имя для обращения по согласованному
алгоритму, т.е. будет использовано уточненное имя. Если же функция реализована на Ассемблере и с ней поработал компилятор Ас7
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
семблера, то уточнения имени не будет, и при попытке обращения
к ней из программы С++ она не будет найдена. Поэтому, либо программист должен хорошо знать алгоритм уточнения имен и давать
функции на Ассемблере уже уточненное имя, либо в программе на
С++ нужно указать, что имя уточнять не следует. Последнее решение, запрещающее корректировку определенных имен, проще реализуется и, кроме того, защищает ассемблерные функции от возможных изменений алгоритма в будущем. Спецификатор extern со
строкой “C” в заголовке функции запрещает уточнение имени
функции. Это необходимо для того, чтобы имя функции было известно для определения в другом модуле, который предполагается
написать на языке Ассемблера, в котором имена при трансляции
если и изменяются, то по другим правилам (может быть изменен
регистр символов, что существенно для языка С++, в котором различаются большие и малые буквы в идентификаторах, но об этой
проблеме будет сказано позже).
Но кроме уточнения в С++ возможны другие изменения имен,
в данном случае к имени функции компилятор добавляет вначале
символ подчеркивания '_'. Такому изменению подвергаются все
глобальные имена. Эту информацию необходимо учитывать при
составлении вызываемых функций на Ассемблере.
Вызывающая программа
Вызывающая программа С++ выполняет такую последовательность действий по вызову функции:
• готовит информацию о передаваемых параметрах в стеке в
порядке, обратном порядку перечисления параметров в заголовке
вызываемой функции;
• сохраняет в стеке адрес точки возврата для вызываемой
функции – это адрес следующей выполняемой команды;
• передает управление в вызываемую функцию;
• после возврата управления из вызываемой функции производит корректировку стека, удаляя из него информацию о параметрах.
Составим текст исходного модуля, содержащего вызывающую
функцию main, и запишем его в файл matr.cpp. Главная функция
main() читает начальные данные из файла matr.dat, обращается к
8
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
функции на Ассемблере с именем max_as и выводит полученные
результаты в файл max.dat.
Листинг 1. Исходный модуль matr.cpp.
#include <fstream.h> // подключение библиотек стандартных функций
#include <conio.h>
#define N 20
// определение константы N в виде макроса
extern "C" int max_as (int n, int m, int a[N][N], int& k, int* p);
//описание внешней функции,
// определенной в другом модуле
void main (void)
{ int n,m;
// заголовок главной функции
// переменные, содержащие размеры
// исходной матрицы
// исходная матрица
// размер выходного массива
// выходной массив
// файл с исходными данными
// формируемый файл
// с выходными данными
int a[N][N];
int k;
int p[N];
ifstream f_in ("matr.dat");
ofstream f_out ("max.dat");
// ввод информации из файла
f_in>>n>>m;
for(int i=0; i<n; i++)
for(int j=0; j<m; j++)
f_in >> a[i][j];
// цикл чтения элементов массива а
int k_ax = max_as (n, m, a, k, p); // обращение к функции на Ассемблере
f_out<<"k_ax="<<k_ax<<" k="<<k<<endl; // вывод результатов в файл f_out
for( i=0; i<k; i++)
f_out << p[i] << " ";
getch ();
return;
// ожидание клавиатуры
}
Передача параметров
При обращении к функции выполняются действия по передаче
параметров. Информация о параметрах помещается в стек, формируется кадр стека с информацией о параметрах и точке возврата.
Порядок сохранения параметров в стеке является обратным поряд9
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
ку, в котором параметры перечислены в списке параметров заголовка функции. Первым в стек помещается последний параметр из
списка в заголовке функции, а последним – первый по списку параметр, т.е. первый по списку параметр оказывается на вершине
стека. Благодаря такому механизму, в языке С и С++ можно создавать функции с переменным числом параметров, пример такой
функции printf() из библиотеки stdio.h.
С любым идентификатором в программе связаны два его атрибута: адрес (указатель) и значение, которое по данному адресу находится. Передавая параметр в вызываемую функцию, нужно точно
представлять, какую информацию об этом параметре мы хотим
поместить в стек: адрес или значение. Зная адрес параметра, можно
получить и его значение, но зная значение параметра, нельзя установить его адрес. Поэтому можно было бы всегда передавать параметры адресом. Однако для входных параметров значение адреса
является излишней информацией. Во многих случаях достаточно
передать лишь значение самого параметра, что упрощает обращение к нему в вызываемой программе. Для выходных же параметров
для их формирования вызываемой программе необходимо знать их
адреса. Адреса передаются в некоторых случаях и для входных параметров. Например, если параметр имеет массивовый тип, то его
значение занимает большой объем памяти, в этом случае передача
значения потребует дополнительных ресурсов времени и памяти,
что приведет к снижению эффективности программы.
Скалярный параметр, имеющий один из основных предопределенных типов, помещается в стек своим значением, в таком случае
говорят, что параметр передается по значению (например, int n, int
m). Если параметр является указателем или ссылкой, то в стек
также помещается его значение, но в этих случаях значением является адрес памяти (например, int& k, int* p ). В нашем примере параметр а описан как массив. Массивы в С++ передаются своим адресом, т.е. адресом первого элемента. Для функций дальнего вызова адреса или указатели помещаются в стек двойным словом
(dword): смещением и сегментным адресом параметра.
Возвращаемое функцией значение может быть скалярного типа.
В этом случае оно возвращается через регистры: al, ax, dx:ax – в
зависимости от того, какое число байт занимает результат: байт,
слово, двойное слово (если предполагается 16-разрядная архитек10
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
тура). В случае возвращения адреса результат имеет объем двойного слова: в ax – возвращается смещение, в dx – сегментный адрес. В нашем примере возвращаемое значение имеет тип int, поэтому вызывающая функция будет ожидать его в регистре ax. После формирования информации о параметрах в стек помещается
адрес точки возврата – адрес следующей за вызовом выполняемой
команды: для близкого вызова – только смещение, для дальнего
вызова – смещение и сегментный адрес точки возврата, и, наконец,
производится передача управления на точку входа вызываемой
функции.
Содержимое стека в этот момент представлено на рисунке 2.
ss :
sp
смещение
сегмент
значение n
значение m
направление смещение а
роста стека сегмент а
смещение k
сегмент k
смещение p
сегмент p
адрес возврата
параметры
старшие
адреса оперативной
памяти
Рис. 2. Содержимое стека в момент передачи управления
После возврата управления из функции вызывающая программа производит корректировку стека: удаляет из стека параметры,
которые были помещены ею в стек для передачи в функцию.
Вызываемая функция на Ассемблере
Итак, обязанности вызываемой функции таковы:
• получив управление, позаботиться о сохранении информации
о регистрах;
11
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
• получить через стек доступ к передаваемым параметрам:
входным и выходным;
• выполнить необходимые действия;
• восстановить содержимое регистров из стека;
• вернуть управление по адресу возврата, находящемуся в стеке.
Составим текст исходного модуля max.asm, содержащий вызываемую функцию _max_as, которая формирует линейный массив, составленный из максимальных по столбцам положительных
элементов переданного ей массива, и находит его длину, т.е. число
элементов, в полученном массиве.
Листинг 2. Исходный модуль max.asm
с вызываемой функцией
public _max_as ;объявление внешнего имени
nconst equ 20
code1 segment
assume cs:code1
_max_as proc far ;описание процедуры дальнего вызова
; сохранение регистров в стеке
push bp
mov bp,sp ; фиксируем точку в стеке
push bx
push ds
push di
push si
push es
; выбор информации о параметрах из стека
mov dx,6[bp]
; параметр n
mov cx,8[bp]
; параметр m
lds bx,10[bp]
; вектор адреса a – в регистрах ds:bx,
; адрес первого элемента a[0][0]
les si,18[bp]
; вектор адреса p – в регистрах es:si
; адрес первого элемента p[0]
cikl: push cx
12
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
; обработка очередного столбца
mov cx,6[bp]
;n
dec cx
; число сравнений
mov di,bx
; адрес начала столбца
mov ax,word ptr[di] ; в регистре ax “отсеиваем” максимум
c1: add di,nconst*2
; подготовка адреса следующего
; элемента столбца
cmp ax,word ptr [di]
jge con
mov ax,word ptr [di]
; найден больший элемент
con: loop c1
; цикл по столбцу
; сейчас в ах – максимальный элемент
; просмотренного столбца
cmp ax,0
; в выходной массив – только
; положительные максимальные элементы
jle np
mov word ptr es:[si],ax
; адрес p[si]
add si,2
; подготовка адреса следующего
; элемента вектора р
np: add bx,2
; подготовка адреса следующего столбца массива а
pop cx
loop cikl
; цикл по столбцам
sub si,word ptr 18[bp]
; вычисление длины массива р
shr si,1
; значение для параметра k
mov ax,si
les di,14[bp]
; адрес параметра k
mov word ptr es:[di],ax
; помещаем k по своему адресу
; восстанавливаем регистры из стека
pop es
pop si
pop di
pop ds
pop bx
pop bp
ret
; не нужно корректировать стек здесь,
; это сделает вызывающая программа
_max_as endp
code1 ends
end
13
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
Имя функции начинается с символа подчеркивания _max_as.
Процедура объявляется заголовком
_max_as proc far
как процедура дальнего вызова (атрибут far) для соответствия с вызовом процедуры, генерируемым в функции main. Атрибут far передает команде run информацию о том, что требуется дальний переход для возврата управления, т.е. адрес возврата находится в стеке как двойное слово.
Директива public _max_as заставляет компилятор поместить
имя _max_as в таблицу внешних имен для разрешения внешних
ссылок на этапе компоновки программы из нескольких модулей.
После получения управления функция сохраняет все используемые регистры в стеке, чтобы иметь возможность их восстановления в конце своей работы. Необходимо оставить содержимое
всех регистров в таком состоянии, которое они имели на момент
начала работы функции, если действия функции не направлены
специально на формирование их содержимого. В последнем случае
восстанавливать специально формируемые регистры не следует.
Этому правилу должны следовать все вызываемые функции и процедуры.
В большинстве случаев для формирования выходного значения
используется регистр ax. В нашем примере предполагается его изменение: в конце работы функции он должен содержать возвращаемое значение. Поэтому его нет среди сохраняемых и восстанавливаемых регистров.
Внутри подпрограммы могут быть рабочие переменные, созданные для промежуточных вычислений, сохранения результатов,
и т.д. Так же, как и в С++, в наших ассемблерных функциях могут
быть созданы локальные переменные. Конечно, можно создать локальные переменные в области данных (например, в сегменте данных), но этого не стоит делать, так как это усложнит структуру
программы (добавление еще одного сегмента) и добавит обязанности по настройке в нужный момент на этот сегмент данных. Функция может использовать стек для размещения в нем рабочей информации, т.е. локальных переменных. Стек как раз и создавался
для временного хранения данных и локальных переменных, так что
именно им и стоит пользоваться. Если функция создает в стеке ло14
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
кальные переменные, то при окончании работы она должна освободить от них стек.
Процедуры на Ассемблере получают доступ к параметрам, сохраненным в стеке, посредством регистра bp. Поэтому его содержимое сохраняется первым:
push bp
.
Регистр bp служит базовым для обращения к сегменту стека.
Если адрес операнда команды Ассемблера формируется на базе регистра bp, то выбор операнда автоматически происходит из сегмента стека. Регистр bp сохраняется в стеке над адресом возврата.
Затем смещение вершины стека, находящееся в регистре sp, копируется в регистр bp, фиксируя точку в стеке, как базовую:
mov bp, sp.
Все смещения для извлечения параметров из стека вычисляются относительно этого адреса. После этого можно сохранять все остальные регистры и пользоваться стеком для временного хранения
информации. При этом обращения к параметрам в стеке будут использовать неизменные смещения относительно базового адреса,
находящегося в регистре bp.
Смещение Содержимое
в стеке
стека
здесь можно
разместить
локальные
s
переменные
+0
bp
смещение
сегмент
+6
значение n
+8
значение m
+10
смещение а
сегмент а
+14
смещение k
сегмент k
+18
смещение p
сегмент p
адрес
возврата
параметры
Рис. 3. Содержимое стека при обращении к параметрам
15
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
Параметры, передаваемые значением, копируются из стека. Например, параметр n копируется в регистр dx командой
mov dx, 6[bp].
Параметр, передаваемый адресом, копируется из стека так, чтобы можно было использовать его значение как адрес для дальнейшей работы. Например, команда
les si, 18 [bp]
копирует в адресный регистр si смещение параметра р из слова
стека с адресом 18[bp], и в сегментный регистр es – сегментный
адрес этого параметра из следующего слова стека с адресом 20[bp].
После выполнения команды к параметру р можно обращаться по
адресу es:si.
Поскольку тело функции содержит два цикла, один из них вложен в другой, то приходится делить регистр cx для организации
обоих циклов. Регистр сх является счетчиком при организации
циклов, он используется командами передачи управления типа
loop. Поэтому вначале тела внешнего цикла командой
cikl: push cx.
содержимое cx временно сохраняется в стеке и перед каждой передачей управления для внешнего цикла
loop cikl
содержимое cx восстанавливается командой
pop cx.
По окончании работы функция восстанавливает содержимое всех
регистров, кроме ax, и передает управление командой ret по адресу возврата, который извлекается из стека, при этом в стеке остается информация о параметрах. Удалить параметры из стека по
правилам языка С++ предстоит вызывающей программе.
Выполнение программы
Последовательность действий для выполнения программы состоит из следующих шагов:
1. Создание файла исходного модуля max.asm с текстом вызываемой функции. Это можно сделать в среде BC++ или в других
текстовых редакторах: например, в блокноте или с помощью
встроенного редактора программы FAR manager: нажатие клавиш
16
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
Shift+F4 позволит создать файл, в который необходимо ввести исходный текст модуля.
2. Трансляция ассемблерного модуля max.asm для получения
объектного модуля max.obj. Это можно сделать командной строкой:
>tasm.exe /ml max.asm, , ;
В командной строке вызова транслятора tasm.exe можно указать
имена файлов выходного объектного модуля и листинга трансляции. В нашем случае эти параметры пропущены, поэтому имена будут сформированы по умолчанию, как max.obj для объектного модуля и max.lst для листинга с сообщениями о процессе трансляции.
Для вызова транслятора tasm.exe, может быть, потребуется указать путь к нему. Транслятор tasm.exe можно найти в пакете
BC++3.1 в директории BIN. Опция /ml необходима для того, чтобы
транслятор не изменял регистр символов имен, так как в языке С++
большие и маленькие буквы различаются. В результате трансляции
получим файл листинга max.lst и файл объектных кодов max.obj.
3. Создание исходного файла matr.cpp с текстом вызывающей
функции.
4. Среда BC++ позволяет произвести компиляцию файла
matr.cpp и компоновку модулей. Для этого создаем проект, который
состоит из двух модулей: matr.cpp и max.obj. Проверяем настройку
директорий (Options/ Directories) для нахождения функций, подключаемых директивой #include, и библиотек объектных кодов для этих
функций. Кроме того необходимо установить модель памяти создаваемого модуля. В пункте меню Options/Compiler/Code Generation
необходимо установить модель памяти
(•) Large,
это означает, что выполняемый модуль может иметь несколько
сегментов как для кода, так и для данных.
Пункт меню Compile/Link создаст исполняемый файл matr.exe,
который и посылается на выполнение.
5. Выполнить программу можно, как указано в предыдущем
пункте, либо в среде ВС++ с помощью пункта главного меню
Run/Run («горячие» клавиши Ctrl-F9).
17
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
Поскольку наша программа считывает исходные данные из
внешнего файла matr.dat, подключаемого при описании переменной f_in
ifstream f_in ("matr.dat"),
то перед выполнением необходимо его создать. Это можно
сделать с помощью какого-либо текстового редактора, например
среды ВС++ или встроенного редактора системной оболочки FAR
или Norton (Shift+F4).
Подготовим тест для выполнения полученной программы
3 5
1 –2 3 –2 7
–2 –5 3 –7 –6
6 0 0 –4 2
и запишем эти данные в файл с именем matr.dat.
В результате выполнения программа создает выходной файл с
именем max.dat. Полученный файл содержит данные:
k_ax=3 k=3
637
Переменная k_ax приняла возвращаемое через регистр ах
значение.
2. Встроенный Ассемблер
В языке С++ есть средство, позволяющее встраивать фрагменты ассемблерного кода в программу С++ для повышения ее эффективности. Например, в случаях, когда прикладная программа полностью берет на себя управление выводом изображения, используя
непосредственное обращение к видеопамяти, скорость вывода становится максимальной. Для реализации такой возможности в языке
С++ существует оператор asm, который имеет следующий синтаксис:
asm <код операции> [<операнды>].
Код операции с операндами или без операндов представляет
команду Ассемблера. Каждая команда записывается в отдельной
строке. Если необходимо написать несколько ассемблерных команд, то можно объединить их в один оператор asm:
18
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
asm{
<код операции> [<операнды>]
<код операции> [<операнды>]
..................................
<код операции> [<операнды>]
}
Комментарии оформляются по правилам языка С++.
Прямое обращение к буферу видеопамяти, находящемуся по
адресу 0b8000h, можно осуществить, используя команды Ассемблера, записанные в программе с помощью оператора asm. Недостатком такой программы является зависимость от физических параметров аппаратуры, что нужно помнить при переносе программы
на другие типы компьютеров.
Пример 2. Очистка экрана
Приведем пример программы, которая производит очистку экрана, записывая в видеопамять число символов пробела с кодом
20h, полностью заполняющих поле текстового экрана. При этом
для каждого символа используется два байта видеопамяти: байт
атрибута с указанием цветов символа и фона и байт кода символа.
Листинг 3. Очистка экрана
void main(void)
{ unsigned int maxy,maxx;
asm{
xor ax,ax
mov es,ax
mov al,es:[484h]
mov maxy,ax
inc maxy
mov ax,es:[44ah]
mov maxx,ax
mov cx,maxy
mul cx
mov cx,ax
mov ax,0b800h
mov di,0
// переменные для размеров текстового экрана
//получение максимального номера строки
// получение длины строки экрана в символах
// счетчик числа выводимых символов
// адрес видеопамяти
19
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
push ax
pop es
mov ax,00720h
rep stosw
}
return;
}
// адрес видеопамяти скопирован в es
// ah – цвет фона и цвет символа (байт rgb)
// 0 – цвет черный, 7 – цвет белый
// повторить maxx*maxy раз
// (число символов на экране)
// asm
Для нахождения числа знакомест (полей для символов) на экране необходимо знать число строк и число колонок на текстовом
экране. Эту информацию можно найти по адресам 484h и 44ah соответственно. Счетчик числа выводимых символов пробела заносим в регистр cx. В регистр di посылаем 0 – смещение первого
байта видеопамяти. Адрес начала сегмента видеопамяти, равный
0b800h, помещаем в сегментный регистр es. Код пробела 20h заносим в регистр al, атрибуты выводимого символа в регистр ah с
помощью команды
mov ax,00720h.
Команда
rep stosw
повторяет копирование информации о выводимом символе пробела
из регистра ax по адресу видеопамяти es:di, увеличивая после
каждого копирования регистр di на 2. Число повторений содержится в регистре сх. После выполнения этой короткой программы
экран очищается.
При создании ассемблерных фрагментов удобно также использовать модульную структуру программы, вынося такие фрагменты
в отдельные функции. Написав тело функции на Ассемблере, можно воспользоваться средствами С++ для организации связи между
вызывающей и вызываемой функциями.
Рассмотрим пример такого использования встроенного Ассемблера.
20
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
Пример 3. Преобразование строки символов
Функция str_pr преобразует строку символов, удаляя из нее
пробелы, и возвращает число символов в преобразованной строке.
Функция str_pr вызывается из главной функции main(). Обе
функции написаны на языке С++. Вызываемая функция использует
в своем теле оператор asm.
Тело функции str_pr фактически написано с помощью ассемблерных команд, а связь между функциями устанавливается с
помощью средств языка С++, т.е. в вызываемой функции str_pr не
нужно явно сохранять регистры и настраивать регистр bp на параметры в стеке, команды для этих действий будут сгенерированы
компилятором С++.
Возвратить управление из подпрограммы необходимо оператором языка C++ (return), который также генерируется компилятором для функции, возвращающей void, если возврат не запрограммирован явно. В теле функции, возвращающей какое-либо
значение, отличное от void, для возврата из подпрограммы должен быть явно указан оператор return с передаваемым в точку вызова значением. Чтобы правильно обращаться к адресам, необходимо знать тип адреса вызываемой функции: далекий или близкий
вызов сгенерирован компилятором при обращении. Этим действием управляет опция в пункте меню Options/Compiler/Code Generation. Чтобы создать функцию близкую, достаточно установить модель памяти
(•) Small.
Это означает, что выполняемый модуль имеет один 64килобайтный сегмент для кода и один сегмент для данных.
Если же необходимо создать функцию ближнего вызова в
большей модели памяти: Medium (один 64-килобайтный сегмент
для данных и несколько сегментов для кода); Large (несколько
сегментов как для кода, так и для данных) или Hugo (такая же как
модель Large, но разрешает данным занимать объем памяти больше, чем 64 К), то нужно использовать спецификацию near в заголовке функции:
int near str_pr (char*s1, char*s2).
21
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
Создадим близкую функцию с двумя параметрами типа char *,
т.е. указатель на символ Функция ближнего вызова при получении
управления имеет кадр стека, в котором адрес возврата содержится
в одном слове – смещением без указания сегментного адреса. Если
мы применили модель памяти Small, все процедуры по определению находятся внутри одного кодового сегмента. Следовательно,
для доступа из программы к любой из них нам необходимо знать
только смещение. Значение смещения занимает объем памяти в 2
байта. Если же мы применяем модель памяти Medium или Large, то
должны сохранить как смещение, так и сегментную часть адреса.
Вместе сегмент и смещение занимают уже 4 байта. Это необходимо учитывать, чтобы правильно выбирать параметры из стека, вычисляя их смещения от фиксированной точки в стеке. В нашем
примере стек при вызове функции содержит следующую информацию:
ss : bp
Смещение
в стеке
+0
+4
+6
Содержимое стека
bp
адрес возврата
адрес первого
параметра
адрес второго
параметра
Рис. 4. Содержимое стека при ближнем вызове
Составим программу, содержащую вызываемую функцию, на
языке С++ с использованием команды встроенного Ассемблера.
Листинг 4. Вызываемая функция для преобразования строк
#include <iostream.h>
#include <conio.h>
#include <string.h>
int str_pr(char*s1,char*s2) // преобразование строки s2 в строку s1
{ asm{
// не нужно сохранять регистры
push ds
pop es
mov di,[bp+4]
// адрес s1
22
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
mov ax,0
mov si,[bp+6]
// адрес s2
// asm
}
cikl:
asm{
cmp byte ptr[si],0
je exit
cmp byte ptr[si],' '
je space
// пропускаем пробел
movsb
// копируем не пробел
jmp cont
}
// asm
space: asm inc si
cont: asm jmp cikl
exit: asm {
mov byte ptr es:[di],0
// записываем символ «конец строки»
sub di,4[bp]
// в регистре di – число символов в строке s1
mov ax,di
}
// asm
return _DI; // псевдопеременная _DI обозначает регистр di
}
Составим текст программы на языке С++ для вызова функции
str_pr.
Листинг 5. Вызывающая программа для преобразования строки
void main(void)
{
char str[ ]="as sem bl er";
char* str1=new char[sizeof(str)];
// определение исходной строки
// выделим память для
// преобразованной строки
clrscr();
// очистка экрана
int lng=str_pr(str1,str);
// обращение к подпрограмме
cout<<"\n \""<<str<<"\" -> \""<<str1<<"\" = "<<lng;
// вывод информации на экран
getch();
// ожидание клавиатуры
}
Исходные данные для выполнения программы заносятся в переменную str[ ] с помощью ее инициализации. Переменная str1,
созданная для преобразованной строки, описывается как указатель
23
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
на символьные данные. С помощью операции new для строки, на
которую она указывает, выделяется динамическая память объемом,
соответствующим размеру исходной строки. Переменная lng с
помощью оператора присваивания получает возвращаемое значение, длину преобразованной строки. В конце выполнения программы на экран через поток cout выводятся значения исходной и
преобразованной строк, а также значение переменной lng.
Компилируется и выполняется программа обычными средствами среды ВС++3.1. После выполнения программы на экране появляется результат работы: исходная строка, преобразованная
строка, число символов в преобразованной строке:
"as sem bl er" –> "assembler" = 9.
3. Вызов функции С++
из ассемблерной программы
Как организовать связь между модулями, если при создании
программы на Ассемблере потребовался вызов функции, написанной на языке высокого уровня С++?
В реальности вряд ли потребуется использовать вызов программы на языке высокого уровня из ассемблерного кода. Одним
из случаев, когда это может потребоваться является необходимость
выполнения сложных вычислений, поскольку вычисления гораздо
проще выполнять на С++, чем на языке Ассемблера. Особенно это
относится к случаю смешанных вычислений, где используются и
значения и с плавающей точкой и целые числа. В этом случае
можно возложить функции по выполнению преобразования типов
и реализации арифметики с плавающей точкой на С++.
Кроме того возможность вызова программы С++ из ассемблерного кода может быть использована на этапе отладки многомодульной программы. Рассмотрим такую возможность связывания
модулей еще и потому, что примеры такого типа позволяют лучше
понять межмодульную связь в системах программ.
24
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
Пример 4. Вычисление суммы двух целых чисел
Ассемблерная программа вызывает функцию С++, которая вычисляет сумму двух целых чисел.
• Создадим файл v.cpp в среде ВС++ 3.1 с исходным текстом
вызываемой функции sum.
Листинг 6. Вызываемая программа на языке С++
для суммы целых
// входные параметры d1, d2, сумма – выходной параметр d3.
extern "C" far void sum(int* d3, int d1,int d2)
{*d3=d1+d2;
return;
}
• Откомпилируем исходный файл в среде ВС++ 3.1:
1) установив директорию для получаемого объектного модуля:
для этого в пункте меню Options\Directories определив директорию в диалоговом окне Output Directory ;
2) установив модель памяти в пункте меню Options/Compiler/Code Generation
(•) Large.
В этом случае атрибут far в заголовке функции sum необязателен, все функции для модуля с моделью памяти Large по умолчанию имеют тип far. Этот атрибут необходим, если установлена модель памяти tiny или small, в которых по умолчанию все функции
имеют тип near.
После успешной компиляции в указанной в пункте 1) директории будет создан объектный файл с именем v.obj.
• Исходный текст вызывающей программы, написанной на Ассемблере, поместим в файл as1.asm.
Листинг 7. Файл as1.asm с текстом вызывающей программы
.model large
; директива для указания модели памяти
DGROUP group _data,stack
; определение группы сегментов
_text segment word public 'CODE' ; сегмент кодов
extrn _sum:far
; объявление внешнего имени
assume cs:_text,ds:data,ss:stack
strt: mov ax,data
25
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
mov ds,ax
push d1
push d2
mov ax,seg summa
push ax
mov ax,offset summa
push ax
call _sum
add sp,8
mov dl,byte ptr summa
add dl,30h
mov ah,2
int 21h
mov ah,4ch
int 21h
_text ends
; настройка на сегмент данных
; подготовка параметров в стеке для
; дальнего вызова
; вызов функции
; корректировка стека
; вывод суммы на экран
; завершение программы
data segment word public 'DATA'
d1 dw 2
d2 dw 3
summa dw 3
data ends
; сегмент данных
stack segment para stack 'STACK'
dw 100 dup(?)
stack ends
; сегмент стека
end strt
; конец исходного текста с указанием точки входа
• Откомпилируем этот исходный модуль с помощью командной строки:
>tasm /mx /zi /o as1.asm,,;
Получим объектный файл as1.obj.
• Компоновщиком соберем оба объектных файла as1.obj и
v.obj в загрузочный:
>tlink as1.obj v.obj;
в результате получим выполняемый файл as1.exe. Компоновщик
tlink.exe можно найти в той же директории, что и компилятор
tasm.еxe, т.е. в пакете ВС в директории BIN.
• Выполним файл as1.exe:
>as1.exe.
На экране появляется результат:
5.
26
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
Пример 5. Вычисления с плавающей точкой
Приведем учебный пример программы на Ассемблере, которая вызывает функцию C++, чтобы выполнить вычисления с плавающей точкой: для последовательности целых чисел найти их
среднее и дисперсию. Программу для решения этой задачи составим из трех частей:
1) главная функция C++ main() читает последовательность целых чисел, передает эту последовательность на обработку ассемблерной процедуре stat; получает от нее среднее значение и среднеквадратичное отклонение; проверяет правило «трех сигм», т.е.
находит процент элементов исходной последовательности, находящихся на расстоянии, большем трех сигм, от среднего значения;
2) ассемблерная процедура stat суммирует числа и в свою
очередь вызывает другую функцию disp() для выполнения вычислений с плавающей точкой;
3) вызываемая функция С++ disp() находит среднее значение,
используя уже найденную сумму, и среднеквадратичное отклонение.
Листинг 8. Файл main.cpp с главной функцией С++
#include <fstream.h>
#include <conio.h>
#include <math.h>
#include <iomanip.h>
#define N 100
extern "C" void stat(int,int*,float*,float*); // описание внешней функции stat
void main(void)
{ int n;
int a[N];
// исходная последовательность
float sred;
// среднее
float sigma;
// среднеквадратичное отклонение
ifstream f("f.dat");
// файл с исходной последовательностью чисел
for(int i=0;!f.eof();i++)
// ввод чисел в массив а
f>>a[i];
n=i–1;
// количество введенных чисел
stat(n,a,&sred,&sigma);
// обращение к функции на Ассемблере
27
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
getch();
cout<<endl<<setw(5)<<setprecision(1)<<"sred="<<sred<<" sigma ="<<sigma;
int count=0;
for( i=0;i<n;i++) // нахождение числа «далеко» отклонившихся элементов
if(fabs(a[i]–sred)>sigma*3)count++;
cout<<endl<<"count="<<count<<"="<<(float)count*100/n<<"%";
getch();
}
На рисунке 5 представлен макет стека с содержимым после обращения к функции
stat(n,a,&sred,&sigma),
сохранения содержимого регистра bp в стеке и копирования адреса вершины стека в этот момент в регистр bp:
strt:
push bp
mov bp,sp.
.
ss : bp
+0
+6
+8
+12
+16
старое содержимое bp
адрес возврата
сегмент
значение n
смещение a
сегмент a
смещение sred
сегмент sred
смещение sigma
сегмент sigma
параметры
Рис. 5. Содержимое стека в начале работы
процедуры stat после сохранения регистра bp
Листинг 9. Файл stat.asm с ассемблерной процедурой stat
.model large
extrn _disp:far
code1 segment
assume cs:code1
public _stat
_stat proc far
; описание внешней функции disp
; определение процедуры дальнего вызова
28
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
strt:
push bp
; сохранение регистров в стеке
mov bp,sp
push si
push bx
push cx
push ax
push ds
push es
pushf
lds
bx,[bp+8] ; адрес исходной последовательности
mov cx,[bp+6]
; число элементов в исходной последовательности
xor ax,ax
; в регистре ах будем накапливать сумму
loops: add ax,word ptr[bx]
add bx,2
loop loops
; формирование кадра стека с параметрами для вызова функции disp
les si,[bp+16]
push es
push si
les si,[bp+12]
push es
push si
push ax
push ds
mov si,[bp+8]
push si
mov ax,[bp+6]
push ax
call _disp
add sp,16
; корректировку стека производит вызывающая программа
popf
; восстановление информации в регистрах
pop es
pop ds
pop ax
pop cx
pop bx
pop si
pop bp
ret
; возврат из процедуры
_stat endp
code1 ends
end
29
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
адрес возврата
сегмент
значение n
смещение a
сегмент a
значение summa
смещение sred
сегмент sred
смещение sigma
сегмент sigma
Рис. 6. Содержимое стека в момент вызова функции disp
Листинг 10. Файл disp.cpp с функцией disp
#include <iostream.h>
#include <conio.h>
#include <math.h>
extern "C" far void disp(int n, int* a, int summa, float* s, float* d)
{
*s=(float)summa/n; // вычисление среднего
float dsp=0;
// переменная для суммы квадратов отклонений
for(int i=0;i<n;i++)
{float r=(float )a[i]–*s;
dsp+=r*r;
}
*d=sqrt(dsp/n);
// вычисление среднеквадратичного отклонения
return;
}
Основная функция main() на языке С++ передает указатель на
массив целых чисел a, длину массива n, указатели на выходные
параметры: среднее и среднеквадратичное отклонение – в функцию
на Ассемблере stat. Эта функция вычисляет сумму целых чисел, а
затем передает указатель на массив целых чисел a, длину массива
n, полученную сумму и указатели на выходные параметры: среднее
и среднеквадратичное отклонение в функцию С++ disp. Функция
30
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
disp приводит сумму к типу с плавающей точкой и вычисляет
среднее значение (делая это с помощью одной строки на С++, в то
время как на Ассемблере для этого потребовался бы внушительный
фрагмент кодов). Функция disp находит среднее значение s и
среднеквадратичное отклонение d и передает управление обратно
в функцию stat.
Для выполнения программы произведем следующие действия:
• Создадим файл f.dat с с исходными данными
1 6 7 3 8 1 2 9 6 7 8 3 4 8 2 4 20 5
• Откомпилируем исходный модуль stat.asm с помощью командной строки:
>tasm /mx /zi /o stat.asm,,;
Получим объектный файл stat.obj.
• В среде ВС++ 3.1 создадим проект, включив в него три файла
main.cpp, disp.cpp, stat.obj.
• Выполним компиляцию и компоновку файлов проекта в
пункте меню Compile\Make, установив модель памяти в пункте меню Options/Compiler/Code Generation
(•) Large.
• Выполним полученную программу в пункте меню Run. В результате выполнения программа выведет на экран следующую информацию:
sred=5.8 sigma=4.3
count=1=5.6%
4. Вызов программой на языке Pascal
функции на Ассемблере
Рассмотрим на примере 6 организацию связи между модулями,
написанными на языках Pascal и Assembler. Для решения задачи
используем среду TP7. Основные соглашения о связях такие же,
как и в рассмотренных выше примерах. Передача информации в
основном производится через стек. Но есть некоторые отличия.
Последовательность помещения параметров в стек в языке Pascal
31
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
является прямой в отличие от обратной в языке Си, т.е. первым в
стеке появляется информация о первом параметре из списка. И за
очистку стека от параметров отвечает вызываемая программа (в Си
стек очищает тот, кто его заполнил, т.е. вызывающая программа).
Пример 6. Скалярное произведение
линейных массивов
Итак, условие задачи заключается в следующем: главная (вызывающая) программа на языке Pascal читает c клавиатуры два одномерных массива одинаковой длины и вызывает процедуру на
Ассемблере, которая находит скалярное произведение исходных
массивов. Процедура на Ассемблере получает через стек всю необходимую информацию: адреса исходных массивов и их длину, а
также адрес выходной переменной, в которую копирует значение
найденного скалярного произведения.
Очевидно, что основная проблема в этой задаче – организация
взаимодействия модулей на языках Pascal и Ассемблере. Как отмечено выше, для языка Pascal характерен прямой порядок включения аргументов в стек: первым в стек записывается первый передаваемый параметр из оператора вызова процедуры. На вершине стека после записи всех передаваемых параметров оказывается
последний из списка параметров. Что же касается очистки стека, в
языке Pascal эту операцию должна производить вызываемая процедура.
Листинг 11. Текст главного модуля svyz.pas на языке Pascal
Рrogram my_pas; {Программа, вызывающая процедуру на Ассемблере }
{$D+}
{Включение полной информации для отладчика}
Uses Сrt;
Type
Mas = Array [0..10] of Byte;
Var
A, B: Mas;
{ описание исходных массивов }
I,
N,
{длина массивов }
Prodact: Integer; { переменная для скалярного произведения }
{$F+}
{Генерация дальнего вызова}
Procedure Pr_v (Var Prodact: Integer; N: Integer; A,B: Mas); External;
32
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
{Процедура Pr_v объявлена как внешняя}
{$L H:\Work\Pr_v.obj} { подключение объектного модуля с процедурой }
Begin
Clrscr;
Writeln('Введите размер массивов') ;
Readln(N);
Writeln('Введите первый массив') ;
For I:=0 To N–1 Do Readln(A[I]);
Writeln('Введите второй массив') ;
For I:=0 To N–1 Do Readln(B[I]);
{Вызов процедуры}
Pr_v (Prodact, N, A, B);
Writeln ('Произведение =', Prodact); { вывод на экран результата }
End.
Оператор вызова процедуры Pr_v помещает в стек информацию о входных и выходных параметрах и адрес возврата. Обращение к параметрам в вызываемой процедуре происходит после сохранения регистра bp в стеке и после копирования вершины стека
в регистр bp. Вычисление адресов для обращения к параметрам
через стек показано на рисунке 7.
ss : bp
+0
+2
+4
+6
+8
+10
+12
+14
+16
+18
bp
Cs
Ip
Смещение B
Сегмент B
Смещение A
Сегмент A
Значение N
Смещение Prodact
Сегмент Prodact
адрес возврата
параметры
14 байт для ret
Рис. 7. Содержимое стека со смещениями
для обращения к параметрам
33
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
Листинг 12. Текст модуля процедуры Pr_v.asm на Ассемблере
; Описание сегмента стека
stac segment para stack 'stack'
db
64 dup('stack')
stac ends
public pr_v
; имя процедуры объявлено внешним
; Описание сегмента кода
code segment
para public'code'
beg: assume
cs:code, ss:stac,ds:data
pr_v proc far
; процедура объявлена как дальняя
push bp
mov
push
push
push
push
mov
mov
mov
xor
cikl: mov
mov
imul
add
inc
inc
loop
mov
mov
pop
pop
pop
pop
pop
ret
pr_v endp
code ends
end
; пролог
bp,sp
si
; сохранение регистров
cx
dx
bx
bx,[bp+6] ; смещение массива В копируем в регистр bx
si,[bp+10] ; смещение массива А копируем в регистр si
cx,[bp+14] ; количество элементов в массиве копируем в cx
dx,dx
;dx=0 начальное значение для суммы
al,[bx]
ah,[si]
ah
; умножение соответствующих элементов массивов
dx,ax
bx
; переход к следующим элементам в массивах
si
cikl
bx,[bp+16] ;смещение переменной Product в bx
[bx],dx
;запись результата
bx
;восстановление регистров
dx
cx
si
bp
14
;очистка стека и возврат из процедуры
;конец процедуры
beg
34
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
Типовая схема организации такой связи следующая:
• Написать процедуру (в примере Pr_v) на Ассемблере дальнего (far) или ближнего (near) типа. Объявить имя этой процедуры
внешним с помощью директивы public:
public pr_v.
Для очистки стека и возврата из процедуры использовать команду ret m, где m количество байт в стеке, которые занимают параметры процедуры (в примере ret 14, так как параметры Prodact,
A, B передаются дальними адресами по четыре байта, сегмент:
смещение), а параметра N – передается своим значением типа
Integer, которое занимает в стеке два байта.
• Для того чтобы процедура на Ассемблере при компоновке с
программой на Pascal воспринималась компилятором как far или
near, недостаточно просто объявить ее таковой в директиве proc.
Кроме этого необходимо включить или выключить опцию компилятора, доступную через меню интегрированной среды TP7:
Options›Compiler›Force far calls.
Установка данной опции заставляет компилятор генерировать
дальние вызовы. Альтернатива данной опции – ключ генерации
дальнего вызова в исходном тексте программы {$F+} или {$F-}
(соответственно, включено или выключено).
• Произвести компиляцию программы Pr_v.asm с целью устранения синтаксических ошибок и получения объектного модуля
программы:
>tasm /zi Pr_v.asm , , , .
Объектный модуль получит имя Pr_v.obj.
• В программе на Pascal, которая будет вызывать внешнюю
процедуру на Ассемблере, следует объявить имя этой процедуры
как внешнее, определенное в другом исходном файле:
Procedure Pr_v (Var Prodact: Integer; N: Integer; A, B: Mas); External;
и директивой компилятора {$L \путь\Pr_v.obj} указать для компилятора путь для нахождения на диске объектного модуля программы Pr_v.obj..
• Если вы собираетесь исследовать в отладчике работу программы, то необходимо либо использовать глобальный ключ
{$D+} в исходном тексте программы, либо установить опцию компилятора в пункте меню среды TP7:
35
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
Options›Compiler›Debug Information.
Использование директив Extrn и Public
для связывания имен переменных
При трансляции программы создаются таблицы имен переменных, которые содержат информацию и типе и адресе переменных.
Существует возможность использовать имена переменных, не определяемых в данном исходном тексте, а созданных какой-либо
другой программой. Такие имена должны иметь статус глобальных, внешних имен. Внешние имена не могут быть обработаны
окончательно при трансляции, поскольку транслятору неизвестны
их адреса. Информация о таких именах сводится в таблицу внешних имен и передается транслятором компоновщику. Компоновщик с помощью этих таблиц разрешает все неразрешенные транслятором связи. Такая модель обмена информацией позволяет избежать сложностей, связанных с сохранением параметров в стеке и
обращением к ним, однако делает вызываемую программу контекстно-зависимой, теряется универсальность использования подпрограммы и, в целом, ее надежность исполняемого файла.
В случае использования несколькими отдельно транслируемыми программами общих переменных необходимо при определении
объявить имена таких переменных внешними. В таком случае, например, подпрограмма, вызванная из главной программы, может
напрямую использовать переменные, определенные в сегменте
данных вызывающей программы.
Синтаксис директив, позволяющих такой обмен информацией,
следующий:
Extern Имя:тип,…, Имя:тип
директива, описывающая тип имени переменной, созданной в
другой программе;
Public Имя,…,Имя
директива, разрешающая использовать переменную другим
программам.
Здесь Имя – идентификатор переменной или процедуры; тип –
для имен переменных указывается один из определенных типов,
например, byte, word, dword; для имен процедур – режим перехода
far или near.
36
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
Пример 7. Использование внешних
имен переменных
Приведем пример использования описанной выше связи имен
связи.
Листинг 13. Программа, вызывающая процедуру на Ассемблере
Рrogram my_pas1;
{$D+}
{Включение полной информации для отладчика}
Uses Сrt;
Type
Mas = Array [0..10] of Byte;
Var
A, B: Mas;
I, N, Prodact: Integer;
Procedure Pr_v1; External; {Процедура Pr_v1 объявлена как внешняя}
{$L H:\Work\Pr_v1.obj}
Begin
Clrscr;
Writeln('Введите размер массивов') ;
Readln (N);
Writeln('Введите первый массив') ;
For I:=0 To N–1 Do Readln(A[I]);
Writeln('Введите второй массив') ;
For I:=0 To N–1 Do Readln(B[I]);
{Вызов процедуры}
Pr_v1;
Writeln ('Произведение =', Prodact);
End.
Листинг 14. Текст модуля Pr_v1.asm на Ассемблере
stac segment para stack 'stack'
db
64 dup('stack')
stac ends
public pr_v1
;Процедура объявлена как внешняя
extrn a:byte,b:byte,n:word,Prodact:word ;Описание внешних переменных
code segment
para public'code' ; Описание сегмента кода
beg: assume
cs:code,ss:stac
pr_v1 proc
; Процедура объявлена как ближняя
mov bx,0
;Смещение относительно начала массива А
mov si,0
;Смещение относительно начала массива В
37
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
mov
xor
cikl: mov
mov
imul
add
inc bx
inc
loop
lea
mov
ret
pr_v1 endp
code ends
end
cx,n
dx,dx
al,a[bx]
ah,b[si]
ah
dx,ax
;Умножение соответствующих элементов массивов
;Переход к следующим элементам в массивах
si
cikl
bx, Product ;Возврат данных через дополнительный сегмент es
es:[bx],dx
beg
Имена a, b, n, Prodact объявлены внешними
extrn a:byte,b:byte,n:word,Prodact:word
и видны в ассемблерной программе, хотя определены в другом исходном тексте на языке Pascal.
Литература
1. Юров, В.И. Assembler : учебник / В.И. Юров. – СПб. : Питер,
2002.
2. Данкан, Р. Профессиональная работа в MS-DOS / Р. Данкан. –
М. : Мир, 1993.
3. Джордейн, Р. Справочник программиста персональных компьютеров типа IBM PC, XT и AT / Р. Джордейн. – М. : Финансы
и статистика, 1992.
4. Пустоваров, В.И. Язык Ассемблера в программировании информационных и управляющих систем / В.И. Пустоваров. – М. :
ЭНТРОП, К. : ВЕК, 1996.
5. Бруно Бабэ. Просто и ясно о Borland C++. – М.: БИНОМ,
1995.
6. Голубь, Н. Искусство программирования на Ассемблере
/ Н. Голубь. – СПб. : ООО ДиаСофтЮп, 2002.
7. Немнюгин, С.А. Turbo Pascal. Программирование на языке
высокого уровня / С.А. Немнюгин. – СПб. : Питер, 2005.
38
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
Оглавление
Введение................................................................................................ 3
1. Вызов программой на языке С++ функции на Ассемблере .... 4
Пример 1. Постановка задачи ............................................................. 5
2. Встроенный Ассемблер ................................................................ 18
Пример 2. Очистка экрана ................................................................ 19
Пример 3. Преобразование строки символов ................................... 21
3. Вызов функции С++ из ассемблерной программы................. 24
Пример 4. Вычисление суммы двух целых чисел .............................. 24
Пример 5. Вычисления с плавающей точкой .................................... 27
4. Вызов программой на языке Pascal функции
на Ассемблере ........................................................................ 31
Пример 6. Скалярное произведение линейных массивов ................. 32
Пример 7. Использование внешних имен переменных ..................... 37
Литература ......................................................................................... 38
39
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
Учебное издание
Связь разноязыковых модулей
Лабораторный практикум на ЭВМ
Методические указания
Составители: Власова Ольга Владимировна
Чаплыгина Надежда Борисовна
Редактор, корректор А.А. Антонова
Компьютерная верстка Е.Л. Шелеховой
Подписано в печать 22.05.2006 г. Формат 60х84/16.
Бумага тип. Усл. печ. л. 2,32. Уч.-изд. л. 1,5.
Тираж 50 экз. Заказ
Оригинал-макет подготовлен
в редакционно-издательском отделе ЯрГУ.
Отпечатано на ризографе.
Ярославский государственный университет.
150000 Ярославль, ул. Советская, 14.
40
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
41
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
Связь
разноязыковых
модулей
Лабораторный практикум на ЭВМ
42
Документ
Категория
Без категории
Просмотров
13
Размер файла
483 Кб
Теги
практикум, разноязыковых, эвм, модулем, 345, связи, лабораторная
1/--страниц
Пожаловаться на содержимое документа