Главная страница Случайная страница КАТЕГОРИИ: АвтомобилиАстрономияБиологияГеографияДом и садДругие языкиДругоеИнформатикаИсторияКультураЛитератураЛогикаМатематикаМедицинаМеталлургияМеханикаОбразованиеОхрана трудаПедагогикаПолитикаПравоПсихологияРелигияРиторикаСоциологияСпортСтроительствоТехнологияТуризмФизикаФилософияФинансыХимияЧерчениеЭкологияЭкономикаЭлектроника |
Newline
Finish code ends end start
Подробно прокомментируем эту программу. Первый параметр функции у нас передаётся по ссылке, а второй – по значению. После выполнения команды вызова процедуры call Summa стековый кадр имеет вид, показанный на рис. 7.2. После полного формирования стековый кадр будет иметь вид, показанный на рис. 7.3.
Сначала отметим особое значение, которое имеет индексный регистр 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 наравне с другими регистрами, а полное разрушение стекового кадра будет производить основная программа путем чтения результата работы функции из стека. Важной особенностью использования стандартных соглашений о связях является и то, что они позволяют производить рекурсивный вызов процедур и функций, причём рекурсивный и не рекурсивный вызовы " по внешнему виду" не отличаются друг от друга. В качестве примера рассмотрим реализацию функции вычисления факториала от неотрицательного целого числа, при этом будем предполагать, что значение факториала поместится в слово. На языке Турбо-Паскаль эта функция имеет следующий вид:
Function Factorial(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
Рассмотрим вызов этой функции 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 equ 30000 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.
Теперь опишем нашу дальнюю процедуру:
|