Newline. Подробно прокомментируем эту программу

Finish

code ends

end start

 

Подробно прокомментируем эту программу. Первый параметр функции у нас передаётся по ссылке, а второй – по значению. После выполнения команды вызова процедуры call Summa стековый кадр имеет вид, показанный на рис. 7.2. После полного формирования стековый кадр будет иметь вид, показанный на рис. 7.3.

Начало стека SS ®  
   
Вершина стека SP ®   Начало стекового кадра ® Адрес возврата
Число элементов N
Адрес начала массива
   
Рис. 7.2. Вид стекового кадра при входе в функцию Summa.

 

Начало стека SS ®    
     
Вершина стека SP ®   База стекового кадра bp ®     Начало стекового кадра ® Локальная переменная S bp-8
Значение регистра cx bp-6
Значение регистра ax bp-4
Значение регистра bx bp-2
Значение регистра bp bp+0
Адрес возврата bp+2
Число элементов N bp+4
Адрес начала массива bp+6
     
  Рис. 7.3. Вид полного стекового кадра (справа показаны смещения слов кадра относительно значения регистра bp).  
         

Сначала отметим особое значение, которое имеет индексный регистр bp при работе со стеком. В архитектуре нашего компьютера это единственный индексный регистр, который предписывает по умолчанию осуществлять запись и чтение данных из сегмента стека. Так команда нашей программы

mov cx,[bp+4]; cx:=длина массива

читает в регистр cx слово, которое расположено по физическому адресу

Афиз = (SS*16 + (4 + <bp>)mod 216)mod 220,

а не по адресу

Афиз = (DS*16 + (4 + <bp>)mod 216)mod 220,

как происходит при использовании на месте bp любого другого индексного регистра, т.е. bx, si или di.

Таким образом, если установить регистр bp внутрь стекового кадра, то его легко использовать для доступа к локальным переменным процедуры или функции. Так мы и поступили в нашей программе, поставив регистр bp примерно на средину стекового кадра. Теперь, отсчитывая смещения от регистра bp вниз, например [bp+4], мы получаем доступ к фактическим параметрам, а, отсчитывая смещение вверх – доступ к сохранённым значениям регистров и локальной переменной, например [bp-8]это адрес локальной переменной, которую в программе на Паскале мы назвали именем S (см. рис. 7.3).

Обратите внимание, что локальные переменные в стековом кадре не имеют имён, что может быть не совсем удобно. В нашем примере мы присвоили локальной переменной имя S при помощи директивы эквивалентности

S equ word ptr [bp-8]

И теперь всюду вместо имени S Ассемблер будет подставлять выражение word ptr[bp-8], которое имеет, как нам и нужно, тип слова. Для порождение этой локальной переменной мы отвели ей место в стеке с помощью команды

sub sp,2; порождение локальной переменной

т.е. просто уменьшили значение регистра-указателя вершины стека на два байта. Этой же цели можно было бы достичь, например, более короткой (но менее понятной для нашей цели) командой

push ax; порождение локальной переменной

Перед возвратом из функции мы начали разрушение стекового кадра, как этого требуют стандартные соглашения о связях. Сначала командой

add sp,2; уничтожение локальной переменной

мы уничтожили локальную переменную, затем восстановили из стека старые значения регистров cx, bx и bp (заметьте, что регистр bp нам больше не понадобится в нашей функции). И, наконец, команда возврата

ret 2*2; возврат с очисткой стека

удаляет из стека адрес возврата и значение двух слов – фактических параметров функции. Унич­тожение стекового кадра завершено.

Продолжение изучения стандартных соглашений о связях мы начнём со следующего замечания. В различных системах программирования стандартные соглашения о связях могут несколько различаться. Например, результат значения функции может возвращаться не на регистре ax, как в нашем последнем примере, а на вершине стека. В этом случае функция должна запоминать и восстанавливать регистр ax наравне с другими регистрами, а полное разрушение стекового кадра будет производить основная программа путем чтения результата работы функции из стека.

Важной особенностью использования стандартных соглашений о связях является и то, что они позволяют производить рекурсивный вызов процедур и функций, причём рекурсивный и не рекурсивный вызовы "по внешнему виду" не отличаются друг от друга. В качестве примера рассмотрим реализацию функции вычисления факториала от неотрицательного целого числа, при этом будем предполагать, что значение факториала поместится в слово. На языке Турбо-Паскаль эта функция имеет следующий вид:

 

FunctionFactorial(N: word): word;

Begin

if N<=1 then Factorial:=1

else Factorial:=N*Factorial(N-1)

End;

 

Реализуем теперь эту функцию в виде близкой процедуры на Ассемблере:

 

Factorial proc near; стандартные соглашение о связях

push bp

mov bp,sp; база стекового кадра

push dx

N equ word ptr [bp+4]; фактический параметр N

mov ax,1; Factorial(N<=1)

cmp N,1

jbe Vozv

mov ax,N

dec ax; N-1

push ax

call Factorial; Рекурсия

mul N; Factorial(N-1)*N

Vozv: pop dx

pop bp

ret 2

Factorial endp

Начало стека SS ®  
   
Вершина стека SP ®     Начало второго кадра ® Конец первого кадра ®     Начало первого кадра ® Значение регистра dx
Значение регистра bp
Адрес возврата
N=4
Значение регистра dx
Значение регистра bp
Адрес возврата
N=5
 
Рис. 7.4. Два стековых кадра функции Factorial.

Рассмотрим вызов этой функции Factorial для вычисления факториала числа 5. Такой вызов можно в основной программе сделать, например, следующими командами:

mov ax,5

push ax

call Factorial

outword ax

На рис. 7.4 показан вид стека, когда произведён первый рекурсивный вызов функции, в стеке при этом два стековых кадра.

В качестве ещё одного примера рассмотрим реализацию с помощью процедуры следующего алгоритма: задана константа N=30000, найти скалярное произведение двух массивов, содержащих по N беззнаковых целых чисел.

На языке Паскаль это можно записать, например, следующим образом: [30]

 

Const N=30000;

Type Mas = array[1..N] of word;

Var A,B: Mas; S: word;

Procedure SPR(var X,Y: Mas; N: integer; var Scal: word);

Var i: integer;

Begin Scal:=0; for i:=1 to N do Scal:=Scal+X[i]*Y[i] end;

 

Перед реализацией этой процедуры SPR на Ассемблере (со стандартными соглашениями о связях) необходимо решить следующие вопросы. Во-первых, сделаем нашу процедуру дальней, чтобы она могла располагаться в любом сегменте памяти нашей программы. Во-вторых, массивы A и B оба не поместятся в один сегмент данных, поэтому нам придётся описать два сегмента данных и поместить в один из них массив A, а в другой сегмент – массив B:

 

N equ30000

D1 segment

A dw N dup (?)

S dw ?

D1 ends

D2 segment

B dw N dup (?)

D2 ends

 

При передаче таких массивов по ссылке нам придётся заносить в стек дальний адрес каждого массива в виде двух чисел <сегмент,смещение>. То же самое придётся делать и для передавае­мой по ссылке переменной S, куда будет помещаться вычисленное значение скалярного произ­ведения. Далее надо решить, как информировать обратившуюся к процедуре основную программу о том, что скалярное произведение не может быть получено правильно, так как не помещается в переменную S. Давайте, например, выделим значение 216-1 (это знаковое число –1) для случая переполнения результата. Эта проблема является типичной в практике программирования: желательно, чтобы каждая процедура и функция выдавали код возврата, который показывает, правильно ли завершилась работа. Таким образом, значение –1 свидетельствует об ошибке, а все остальные значения переменной S будут означать правильное завершение работы нашей процедуры (т.е. правильное значение скалярного произведение, равное 216-1 мы тоже, к сожалению, объявим ошибочным).

Напишем теперь фрагмент программы для вызова процедуры скалярного произведения:

 

mov ax,D1

push ax

mov ax,offset A

push ax; Полный адрес массива A

mov ax,D2

push ax

mov ax,offset B

push ax; Полный адрес массива B

mov ax,N

push ax; Длина массивов

mov ax,D1

push ax

mov ax,offset S

push ax; Полный адрес S

call SPR

 

Для такого вызова при входе в процедуру стековый кадр будет иметь вид, показанный на рис. 7.5.

       
  Вершина стека SP ®   Начало стекового кадра ® IP адреса возврата  
    CS адреса возврата  
    Адрес S в D1  
    Адрес сегмента D1  
    Число элементов N  
    Адрес массива B в D2  
    Адрес сегмента D2  
    Адрес массива A в D1  
  Начало стекового кадра ® Адрес сегмента D1  
       
Рис. 7.5. Стековый кадр при входе в процедуру скалярного произведения.
       

Теперь опишем нашу дальнюю процедуру:

 








Дата добавления: 2015-10-05; просмотров: 627;


Поиск по сайту:

При помощи поиска вы сможете найти нужную вам информацию.

Поделитесь с друзьями:

Если вам перенёс пользу информационный материал, или помог в учебе – поделитесь этим сайтом с друзьями и знакомыми.
helpiks.org - Хелпикс.Орг - 2014-2024 год. Материал сайта представляется для ознакомительного и учебного использования. | Поддержка
Генерация страницы за: 0.019 сек.