Стандартные соглашения о связях
Сначала поймём необходимость существования некоторых стандартных соглашений о связях между процедурой и основной программой. Действительно, иногда программист просто не сможет "договориться", например, с процедурой, как она должна принимать свои параметры. В качестве первого примера можно привести так называемые библиотеки стандартных процедур. В этих библиотеках собраны готовые процедуры, реализующие алгоритмы для некоторой предметной области (например, для работы с матрицами). Такие библиотеки обычно поставляется в виде набора так называемых объектных модулей, что исключает возможность вносить изменения в исходный текст этих процедур (с объектными модулями мы познакомимся далее в нашем курсе).
Другим примером является написание частей программы на нескольких языках программирования, при этом чаще всего основная программа пишется на некотором языке высокого уровня (Фортране, Паскале, С и т.д.), а процедура – на Ассемблере. Вспомним, что когда мы говорили об областях применения Ассемблера, то одной из таких областей и было написание процедур, которые вызываются из программ на языках высокого уровня. Например, для языка Турбо-Паскаль такая, как говорят, внешняя, функция может быть описана следующим образом:
FunctionSumma(Var A: Mas, N: integer): integer;
External;
Служебное слово External является указанием на то, что эта функция описана не в данной программе и Паскаль-машина должна вызвать эту внешнюю функцию, как-то передать ей параметры и получить результат работы функции. Если программист пишет эту функцию на Ассемблере, то он конечно никак не может "договориться" с Паскаль-машиной, как он хочет получать параметры и возвращать результат работы своей функции.
Именно для таких случаев и разработаны стандартные соглашения о связях. При этом если процедура или функция, написанная на Ассемблере, соблюдает эти стандартные соглашения о связях, то это гарантирует, что эту процедуру или функцию можно будет вызывать из программы, написанной на другом языке программирования, если в нём тоже соблюдаются такие же стандартные соглашения о связях.
Рассмотрим типичные стандартные соглашения о связях, обычно они включают следующие пункты.
· Фактические параметры перед вызовом процедуры или функции записываются в стек.[26] При передаче параметра по значению в стек записывается это значение, а в случае передачи параметра по ссылке в стек записывается адрес начала фактического параметра.[27] Порядок записи фактических параметров в стек может быть прямым (сначала записывается первый параметр, потом второй и т.д.) или обратным (когда, наоборот, сначала записывается последний параметр, потом предпоследний и т.д.). В разных языках программирования этот порядок различный. Так, в языке С это обратный порядок, а в большинстве других языков программирования высокого уровня – прямой. [28]
· Если в процедуре или функции необходимы локальные переменные, то место им отводится в стеке. Обычно это делается путём увеличения размера стека, для чего, как мы уже знаем, надо уменьшить значение регистра SP на число байт, которые занимают эти локальные переменные.
· Функция возвращает своё значение в регистрах al, ax или в паре регистров <dx,ax>, в зависимости от величины этого значения. Для возврата значений, превышающих двойное слово, устанавливаются специальные соглашения.
· Если в процедуре или функции изменяются регистры, то в начале работы необходимо запомнить значения этих регистров в локальных переменных, а перед возвратом – восстановить эти значения (для функции, естественно, не запоминаются и не восстанавливаются регистр(ы), на котором(ых) возвращается результат её работы). Обычно также не запоминаются и не восстанавливаются регистры для работы с вещественными числами.
· Перед возвратом из процедуры и функции стек очищается от всех локальных переменных, в том числе и от фактических параметров (вспомним, что в языке Паскаль формальные параметры, в которые передаются соответствующие им фактические параметры, тоже являются локальными переменными процедур и функций!).
Участок стека, в котором процедура или функция размещает свои локальные переменные (в частности, фактические параметры) называется стековым кадром (stack frame). Стековый кадр начинает строить основная программа перед вызовом процедуры или функции, помещая туда фактические параметры. Затем команда передачи управления с возвратом call помещает в стек адрес возврата (это одно слово для близкой процедуры и два – для дальней). Далее уже сама процедура или функция продолжает построение стекового кадра, размещая в нём свои локальные переменные.
Заметим, что если построением стекового кадра занимаются как основная программа, так и процедура (функция), то полностью разрушить стековый кадр должна процедура (функция), так что при возврате в основную программу стековый кадр будет уже уничтожен.[29]
Перепишем теперь нашу последнюю программу с использованием стандартного соглашения о связях. Будем предполагать, что передаваемый по ссылке адрес фактического параметра-массива занимает одно слово (т.е. является смещением в сегменте данных). Для хранения стекового кадра (локальных переменных функции) зарезервируем в стеке 32 слова. Ниже показано возможное решение этой задачи.
include io.asm
data segment
X dw 100 dup(?)
Y dw 200 dup(?)
Sum dw ?
data ends
stack segment stack
dw64 dup (?); для системных нужд
dw 32 dup (?); для стекового кадра
stack ends
code segment
assume cs:code,ds:data,ss:stack
Summa proc near
; стандартные соглашение о связях
push bp
mov bp,sp; база стекового кадра
push bx
push ax
push cx; запоминание регистров
sub sp,2; порождение локальной переменной
S equ word ptr [bp-8]
; имя S будет эквивалентным адресу локальной переменной
mov cx,[bp+4]; cx:=длина массива
mov bx,[bp+6]; bx:=адрес первого элемента
mov S,0; сумма:=0
L: mov ax,[bx];сложение двумя командами,
add S,ax; так как нет формета память-память
add bx,2
loop L
mov ax,S; результат функции
add sp,2; уничтожение локальной переменной
pop cx
pop ax
pop bx
pop bp; восстановление регистров cx, bx и bp
ret 2*2
; возврат с очисткой стека от фактических параметров
Summa endp
start:mov ax,data
mov ds,ax
; здесь команды для ввода массивов X и У
mov ax, offset X; адрес начала X
push ax; первый фактический параметр
mov ax,100
push ax; второй фактический параметр
call Summa
mov Sum,ax; сумма массива X
mov ax, offset Y; адрес начала Y
push ax; первый фактический параметр
mov ax,200
push ax; второй фактический параметр
call Summa
add Sum,ax; сумма массивов X и Y
outint Sum
Дата добавления: 2015-10-05; просмотров: 904;