Главная | Обратная связь | Поможем написать вашу работу!
МегаЛекции

Тема 5.1 Программирование циклов. Циклы с предусловием.




 

 

Структурное программирование

Методология структурного программирования основана на предположении, что логичность и понятность программы обеспечивает надежность, облегчает модификацию и ускоряет разработку программы.

Характерными чертами структурного программирования являются:

отказ от неструктурных передач управления;

ограниченное использование глобальных переменных;

модульность.

Цикл с предусловием

Существует три вида циклов: while, for и do. Цикл while имеет следующую форму:

while (e) s;

Оператор s выполняется до тех пор, пока значение выражения e равно " истина". Значение e вычисляется перед каждым выполнением оператора s.

В предшествующих лекциях мы пользовались этой формой цикла. Рассмотрим еще один пример его работы:

/* угадывание числа */

#include < stdio. h>

main( )

{

       int i = 1;

       char res;

       printf(" Задумайте целое число от 1 до 100.

                   Компьютер попытается угадать его. \n" );

       printf (" Отвечайте y, если догадка правильна и" );

       printf(" \n n, если программа ошибается \n" );

       printf(" Итак, ваше число %d? \n", i);

       /*получение ответа */

       while((res = getchar( ))! ='y')

       if(res! ='\n')

       /* пропуск символа новая строка */

                   printf(" Ну тогда оно равно %d\n" , ++i);

       printf(" Число угадано! \n" );

}

! Это довольно простая программа. Она написана правильно и решает поставленную задачу, но делает это крайне неэффективно. Данный пример показывает, что правильность написания - не единственный критерий, по которому необходимо оценивать программу. При этом очень важна ее эффективность!

В наших примерах до сих пор использовались условные выражения, но вообще говоря, это могут быть выражения произвольного типа. В качестве оператора можно использовать простой оператор с символом " точка с запятой" в конце или составной оператор, заключенный в фигурные скобки. Если выражение истинно (или в общем случае равно единице), то оператор, входящий в цикл while, выполняется один раз, а затем выражение проверяется снова. Эта последовательность действий, состоящая из проверки и выполнения оператора, периодически повторяется до тех пор, пока выражение не станет ложным (или в общем случае равным нулю). Каждый такой шаг называется итерация. Данная структура аналогична структуре оператора if. Основное отличие заключается в том, что в операторе if проверка условия и (возможное) выполнение оператора осуществляется только один раз, а в цикле while эти действия производятся, вообще говоря, неоднократно.

! При построении цикла while вы должны включить в него какие-нибудь конструкции, изменяющие величину проверяемого выражения так, чтобы в конце концов оно стало ложным. В противном случае выполнение цикла никогда не завершится.

Цикл while является условным циклом, использующим предусловие, т. е. условие на входе. Он называется условным, потому что выполнение оператора зависит от истинности условия, описываемого с помощью выражения. Подобное выражение задает предусловие, поскольку выполнение этого условия должно быть проверено перед началом выполнения тела цикла.

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

Подведем итоги.

Оператор while определяет операции, которые циклически выполняются до тех пор, пока проверяемое выражение не станет ложным, или равным нулю. Оператор while - это цикл с предусловием. Решение, выполнить ли в очередной раз тело цикла, принимается перед началом его прохождения. Поэтому вполне возможно, что тело цикла не будет выполнено ни разу. Оператор, образующий тело цикла, может быть либо простым, либо составным. Форма записи:

while (выражение) оператор

Выполнение оператора циклически повторяется до тех пор, пока выражение не станет ложным, или равным нулю.

Цикл со счетчиком

Оператор цикла for

for(e1; e2; e3) s

является удобной сокращенной записью для цикла while вида

e1;

while(e2) {

       s;

       e3;

}

Выражение e1 служит для задания начальных условий выполнения цикла, выражение e2 обеспечивает проверку условия выхода из цикла, а выражение e3 модифицирует условия, заданные выражением e1. Любое из выражений e1, e2, e3 может быть опущено. Если опущено e2, то по умолчанию вместо него подставляется значение TRUE. Например, цикл for

for(; e2; ) s;

с опущенными e1, e3 эквивалентен циклу

while(e2) s;

Цикл

for(;; ) s;

со всеми опущенными выражениями эквивалентен циклу

while(TRUE) s;

т. е. эквивалентен бесконечному циклу. Такой цикл может быть прерван только явным выходом из него с помощью операторов break, goto, return, содержащихся в теле цикла s.

 

44.               Циклы с постусловием. Циклы с параметром. Вложенные циклы.

 

В Си существуют все три типа операторов цикла: цикл с предусловием, цикл с постусловием и цикл с па­раметром.

Цикл с предусловием. Формат оператора цикла с предусловием:

while (выражение) оператор;

Цикл повторяет свое выполнение, пока значение выражения от­лично от нуля, т. е. заключенное в нем условие цикла истинно.

В качестве примера использования оператора цикла рассмот­рим программу вычисления факториала целого положительного числа N!. Сопоставим программу решения этой задачи, написан­ную на Си.

// Программа вычисления факториала

#include < iostream. h>

void main()

{ long int F;

int i, N;

cout< < " N=";

cin> > N;

F=i=l;

while(i< =N) F=F*i++;

cout< < " \n" < < N< < " ! =" < < F;

}

Обратите внимание на операторы в теле цикла. Конечно, и в Си-программе можно было написать два оператора присваива­ния, объединив их фигурными скобками. Однако использован­ный способ записи более лаконичен и более характерен для Си. Этот же самый оператор можно было записать еще короче: F*=i++

При практическом использовании этой программы не следует забывать, что факториал — очень быстро растущая функция, и поэтому при определенных значениях N выйдет из диапазона, со­ответствующего типу long int. Задав для переменной F тип unsigned long, можно сдвинуть эту границу, но этого может оказаться недостаточно. Предлагаем в качестве самостоятельного задания исследовать предельные значения N для двух указанных типов переменной F.

Интересно свойство следующего оператора:

while(1);

Это бесконечный пустой цикл. Использование в качестве вы­ражения константы 1 приводит к тому, что условие повторения цикла все время остается истинным и работа цикла никогда не заканчивается. Тело в этом цикле представляет собой пустой опе­ратор. При исполнении такого оператора программа будет «топ­таться на месте».

Рассмотрим еще один пример использования оператора цикла while. Вернемся к задаче итерационного вычисления суммы гар­монического ряда: 1 + 1/2 + 1/3 +... с заданной точностью ε. Снова сопоставим программу на С++.

// сумма гармонического ряда

#include < iostream. h>

#include < limits. h>

void main()

{int n=l;

double S=0, eps;

cout< < " Toчность: ";

cin> > eps;

while(1. 0/n> eps & & n< INT_MAX)

S+=l. /n++;

cout< < " \nCyммa=" < < S;

}

Файл limits. h, подключаемый препроцессором, содержит оп­ределения предельных констант для целых типов данных. В част­ности, константа с именем int_max равна максимальному значе­нию типа int в данной реализации компилятора. Если для типа int используется двухбайтовое представление, то int_max=327 67. В этом же заголовочном файле определены и другие константы: INT_MIN=-327 68; LONG_MAX=214 74 8 3 64 7 и т. д.

Цикл с постусловием. Формат оператора цикла с постусловием:

do оператор while (выражение);

Цикл выполняется до тех пор, пока выражение отлично от нуля, т. е. заключенное в нем условие цикла истинно. Выход из Цикла происходит после того, как значение выражения станет ложным, иными словами равным нулю. В качестве при­мера рассмотрим программу вычисления N!, в которой используется цикл с постусловием.

#include < iostream. h>

void main()

{ long int F;

int i, N;

cout< < " N="; cin> > N;

F=i=1;

do F*=i++;

while(i< =N);

cout< < " \n" < < N< < "! =" < < F; }

Цикл с параметром. Формат оператора цикла с параметром:

for (выражение_1; выражение_2; выражение_3) оператор;

Выражение 1 выполняется только один раз в начале цикла. Обыч­но оно определяет начальное значение параметра цикла (инициа­лизирует параметр цикла). Выражение 2 — это условие выполне­ния цикла. Выражение 3 обычно определяет изменение параметра цикла, оператор — тело цикла, которое может быть простым или составным. В последнем случае используются фигурные скобки.

 

 

Рис 4

Алгоритм выполнения цикла for представлен на блок-схеме на рис. 4.

Обратите внимание на то, что после вычисления выражения 3 происходит возврат к вычислению выражения 2 — проверке условия повторения цикла.

С помощью цикла for нахождение N! можно организовать следующим обра­зом:

F=l;

for(i=l; i< =N; i++) F=F*i;

Используя операцию «запятая», мож­но в выражение 1 внести инициализа­цию значений сразу нескольких пере­менных:

for(F=l, i=l; i< =N; i++) F=F*i;

Некоторых элементов в оператора for может не быть, однако разделяющие их точки с запятой обязательно должны присутствовать. В сле­дующем примере инициализирующая часть вынесена из операто­ра for:

F=1;

i=1;

for(; i< =N; i++) F=F*i;

Ниже показан еще один вариант вычисления N!. В нем на месте тела цикла находится пустой оператор, а вычислительная часть внесена в выражение 3.

for(F=l, i=l; i< =N; F=F*i, i++);

Этот же оператор можно записать в следующей форме:

for(F=l, i=l; i< =N; F*=i++);

В языке Си оператор for является достаточно универсальным средством для организации циклов. Вот пример вычисления суммы элементов гармонического ряда, превышающих заданную величину ε:

for(n=l, S=0; 1. 0/n> eps & & n< INT_MAX; n++) S+=1. 0/n;

И наконец, эта же самая задача с пустым телом цикла:

for(n=l, S=0; 1. 0/n> eps & & n< INT_MAX; S+=l. 0/n++);

Следующий фрагмент программы на Си++ содержит два вло­женных цикла for. В нем запрограммировано получение на экране таблицы умножения.

for(х=2; х< =9; х++)

for(y=2; y< =9; y++)

cout< < " \n" < < x< < " *" < < y< < " = " < < x*y;

На экране будет получен следующий результат:

2*2=4

2*3=6

………

9*8=72

9*9=81

Оператор continue. Если выполнение очередного шага цикла требуется завершить до того, как будет достигнут конец тела цик­ла, используется оператор continue. Следующий фрагмент программы обеспечивает вывод на экран всех четных чисел в диапа­зоне от 1 до 100.

for< i=l; i< =100; i

{if(i%2) continue;

cout< < " \t" < < i; }

Для нечетных значений переменной i остаток от деления на 2 будет равен единице, этот результат воспринимается как значе­ние «истина» в условии ветвления, и выполняется оператор continue. Он завершит очередной шаг цикла, выполнение цикла перейдет к следующему шагу.

Оператор goto. Оператор безусловного перехода goto суще­ствует в языке Си, как и во всех других языках программирования высокого уровня. Однако с точки зрения структурного подхода к программированию его использование рекомендуется ограничить. Формат оператора:

goto метка;

Метка представляет собой идентификатор с последующим дво­еточием, ставится перед помечаемым оператором.

Одна из ситуаций, в которых использование goto является оправданным — это необходимость «досрочного» выхода из вло­женного цикла. Вот пример такой ситуации:

for(... )

{ while (... )

{ for(... )

{... goto exit... }

}

}

exit: cout< < " Bыход из цикла";

При использовании оператора безусловного перехода необхо­димо учитывать следующие ограничения:

o нельзя входить внутрь блока извне;

o нельзя входить внутрь условного оператора (if... else... );

o нельзя входить внутрь переключателя;

o нельзя входить внутрь цикла.

 

 

45.               Организация подпрограмм

 

Процедура может размещаться в любом месте программы, но так, чтобы на нее случайным образом не попало управление. Если процедуру просто вставить в общий поток команд, то процессор воспримет команды процедуры как часть этого потока и, соответственно, начнет выполнять эти команды. Учитывая это обстоятельство, есть следующие варианты размещения процедуры в программе:

· в начале программы (до первой исполняемой команды);

· в конце программы (после команды, возвращающей управление операционной системе);

· промежуточный вариант — внутри другой процедуры или основной программы (в этом случае необходимо предусмотреть обход процедуры с помощью команды безусловного перехода JМР);

· в другом модуле (библиотеке DLL).

Размещение процедуры в начале сегмента кода предполагает, что последовательность команд, ограниченная парой директив PROC и ENDP, будет размещена до метки, обозначающей первую команду, с которой начинается выполнение программы. Эта метка должна быть указана как параметр директивы END, обозначающей конец программы:

model small

. stack 100h

. data

. code

my_proc procnear

ret

my_proc endp

start:

end start

Объявление имени процедуры в программе равнозначно объявлению метки, поэтому директиву PROC в частном случае можно рассматривать как завуалированную форму определения программной метки. Поэтому сама исполняемая программа также может быть оформлена в виде процедуры, что довольно часто и делается с целью пометить первую команду программы, с которой должно начаться выполнение. При этом не забывайте, что имя этой процедуры нужно обязательно указывать в заключительной директиве END. Так, последний рассмотренный фрагмент эквивалентен следующему:

model small

. stack 100h

. data

. code

my_proc procnear

ret

my_proc endp

start proc

start endp

end start

В этом фрагменте после загрузки программы в память управление будет передано первой команде процедуры с именем start.

Размещение процедуры в конце программы предполагает, что последовательность команд, ограниченная директивами PROC и ENDP, находится следом за командой, возвращающей управление операционной системе:

model small

. stack 100h

. data

. code

start:

mov ax, 4c00h

int 21h; возврат управления операционной системе

my_proc procnear

ret

my_proc endp

end start

Промежуточный вариант расположения тела процедуры предполагает ее размещение внутри другой процедуры или основной программы. В этом случае необходимо предусмотреть обход тела процедуры, ограниченного директивами PROC и ENDP, с помощью команды безусловного перехода JМР:

 

 

46.               Передача параметров через регистры, статическую память, стек, область за командой вызова.

 

Процедура, часто называемая также подпрограммой, — это основная функциональная единица декомпозиции (разделения на несколько частей) некоторой задачи.

Процедура представляет собой группу команд для решения конкретной подзадачи

Процедура - именованная, правильным образом оформленная группа команд, которая, будучи однократно описана, при необходимости может быть вызвана по имени любое количество раз из различных мест программы.

 

Описание процедуры

Для описания последовательности команд в виде процедуры в языке ассемблера используются две директивы: PROC и ENDP.

Синтаксис описания процедуры таков

< имя> proc [тип]

< тело процедуры>

< имя> endp

 

< Тип> может принимать значения near или far и характеризует возможность обращения к процедуре из другого сегмента кода. Тип near позволяет обращаться к процедуре только из того сегмента кода, в котором она описана. К процедуре типа FAR можно обращаться и из других сегментов тоже. По умолчанию тип принимает значение near.

Замечание. Имя процедуры по сути является меткой, т. е. с помощью простых команд перехода можно осуществить переход на первую команду процедуры.

 

Размещение в сегменте кода

Процедура может размещаться в любом месте программы, но так, чтобы на нее случайным образом не попало управление.

Если процедуру просто вставить в общий поток команд, то микропроцессор будет воспринимать команды процедуры как часть этого потока и соответственно будет осуществлять выполнение команд процедуры.

Если имеется несколько процедур их обычно размещают рядом.

1. В начале сегмента кода

!!! Процедура должна быть расположена до первой выполняемой команды

Пример. . code

name proc; ближняя по умолчанию

. . .

name endp

start: . . .

end start

2. В конце сегмента кода

!!! Процедура должна быть размещена после команды, возвращающей управление ОС

Пример. code

start: . . .

mov ax, 4C00h

int 21h

name proc near

. .

name endp

end start

 

3. Можно разместить процедуру внутри сегмента кода

!!! Необходимо предусмотреть обход процедуры

Пример. code

start: . . .

jmp metka

name proc

. . .

name endp

metka: . . .

end start

4. Процедура может быть описана в другом сегменте кода (об этом позже)

 

Вызов процедур

В принципе можно было бы обратиться к процедуре, просто переходом на метку - имя процедуры. Но!!! Необходимо сохранить где-то адрес возврата, т. е. адрес команды, следующей после процедуры.

В систему команд микропроцессора была введена специальная команда

Вызов процедуры, или переход с возвратом

CALL [модификатор] < имя процедуры>

Команда записывает адрес следующей за ней команды в стек, а затем осуществляет переход по метке < имя процедуры>.

Вспомним, что адрес следующей выполняемой команды задается парой CS: IP.

Если процедура описана как

 дальняя, то в стек по очереди заносятся два значения CS, IP.

 короткая - только значение IP

И при этом соответственно переход считается коротким или дальним.

Поэтому в с-ме команд имеется два варианта команды CALL.

!!! Ассемблер выберет правильный вариант, если процедура была описана до вызова. Если процедура будет описана позже транслятор считает процедуру ближней и формирует команду ближнего вызова. Если процедура окажется дальней, будет зафиксирована ошибка.

Для указания транслятору на то, что процедура, описанная ниже, будет дальней можно использовать < модификатор> со значением FAR PTR

Например, CALL FAR PTR NAME

Ì î ä è ô è ê à ò î ð ì î æ å ò ï ð è í è ì à ò ü ñ ë å ä ó þ ù è å ç í à ÷ å í è ÿ:

•near ptr — ï ð ÿ ì î é ï å ð å õ î ä í à ì å ò ê ó â í ó ò ð è ò å ê ó ù å ã î ñ å ã ì å í ò à ê î ä à. Ì î ä è ô è ö è ð ó å ò ñ ÿ ò î ë ü ê î ð å ã è ñ ò ð eip/ip

•far ptr — ï ð ÿ ì î é ï å ð å õ î ä í à ì å ò ê ó â ä ð ó ã î ì ñ å ã ì å í ò å ê î ä à. À ä ð å ñ ï å ð å õ î ä à ç à ä à å ò ñ ÿ ð å ã è ñ ò ð ами cs è ip/eip;

•word ptr — ê î ñ â å í í û é ï å ð å õ î ä í à ì å ò ê ó â í ó ò ð è ò å ê ó ù å ã î ñ å ã ì å í ò à ê î ä à. Ì î ä è ô è ö è ð ó å ò ñ ÿ ò î ë ü ê î eip/ip.

•dword ptr — ê î ñ â å í í û é ï å ð å õ î ä í à ì å ò ê ó â ä ð ó ã î ì ñ å ã ì å í ò å ê î ä à. Ì î ä è ô è ö è ð ó þ ò ñ ÿ (ç í à ÷ å í è å ì è ç ï à ì ÿ ò è — è ò î ë ü ê î è ç ï à ì ÿ ò è, è ç ð å ã è ñ ò ð à í å ë ü ç ÿ ) î á à ð å ã è ñ ò ð à, cs è eip/ip.

 

Возврат из процедур

Адрес возврата в вызывающую программу хранится в стеке.

Команда RET [число] возвращает управление вызывающей программе.

Она считывает адрес возврата из вершины стека, загружает его в регистры CS и IP (теперь выполняться будет следующая за CALL команда в программе) адрес возврата при этом удаляется из стека(! ), затем стек очищается на указанное число байт и выполняется переход по адресу возврата.

Команда RET соответствует команде RET 0

В зависимости от того, дальняя или ближняя была описана процедура, ассемблер формирует одну из возможных команд RET. В первом случае из стека извлекается два слова, которые загружаются в регистры CS и IP, во втором - одно слово в регистр IP.

Команды CALL и RET действуют согласованно, за соответствием между командами следит транслятор.

Необязательный параметр [число] задает значение, удаляемое из стека при возврате из процедуры - в байтах (use16) или в словах (use32)

 

Параметры процедур

Если в процедуру необходимо передать некоторую переменную, то для процедуры она является формальным параметром. А конкретное значение данной переменной, которое передается в процедуру для замещения соответствующего формального параметра, является фактическим параметром. Например, если описать некоторую процедуру для сложения двух чисел как SUM(x, y), то переменные x, y явл. формальными параметрами, а при обращении к процедуре для вычисления суммы 4 и 5

SUM(4, 5) значения 4 и 5 являются фактическими парметрами.

Поэтому одну и ту же процедуру можно использовать многократно для разных наборов значений этих переменных, SUM(5, 7) SUM (100, 45)

Передача значений в процедуру называется передачей параметров.

Результатом выполнения процедуры может быть некоторое значение, которое необходимо передать вызывающей программе. Передача результата из процедуры в программу тоже организовывается как передача параметров.

Передавать параметры можно

 через регистры

 через стек

 через общую область памяти

 с помощью директив extern и public

 

Существует два способа передачи данных:

1. Передача параметров по значению

В этом случае передаются сами данные, т. е. их значения

!!! При передаче параметров по значению в процедуре обрабатываются их копии. Поэтому значения переменных в основной программе не изменяются!!!

2. Передача параметров по ссылке

В этом случае передаются адреса (ссылки) параметров

!!! Здесь обрабатываются не копии, а сами данные, поэтому при изменении данных в процедуре они автоматически изменяются и в вызывающей программе!!!

Т. Е. Передача параметра по ссылке означает передачу адреса ячейки, соответствующей фактическому параметру.

 

Передача параметров через регистры

Самый простой способ передачи данных. Его суть заключается в следующем:

Основная программа записывает фактические параметры в какие-нибудь регистры, а процедура берет их оттуда и обрабатывает.

Аналогично поступают с результатом. Процедура помещает результат в какой-то регистр, а программа извлекает его оттуда.

!!! Если размер данных превышает 16 или 32 бит (размер регистра), то передавать нужно не сами данные, а ссылки на них.

Совет 1 Рекомендуется тщательно комментировать процедуру и основную программу.

Необходимо описать в какие регистры какие параметры помещаются, в какие регистры помещается результат.

Совет 2. Рекомендуется использовать при небольшом количестве параметров, т. к. число доступных регистров ограничено.

Пример1 Передача параметров по значению

Вычислить max(a, b) + max(5, a-1)

Решение. Опишем процедуру вычисления максимума двух переменных.

Для передачи первого параметра будем исп-ть регистр ax, для второго - регистр BX. Результат процедура будет помещать в регистр AX.

model small

. stack 128

. data

A dw?

B dw?

REZ dw?

. code

; процедура вычисляет максимальное из двух чисел

; первое число в AX, второе - в BX

; результат помещается в AX

MAX proc

cmp ax, bx

jge M

mov ax, bx

M: ret

MAX endp

start: . . .

mov ax, A

mov ax, B

call MAX; результат в AX

mov rez, ax

mov ax, 5

mov bx, A

dec bx

call MAX; результат в AX

add rez, ax

. . .

end start

Пример 2 Передача параметра по ссылке

Число (неотриц) разделить на 16. И оставить там же, где находилось

Для сравнения с паскалем procedure DEL (var x: integer) Параметр-переменная

Осн. программа записывает в регистр адрес фактической переменной.

В качестве регистра необходимо выбирать регистр-модификатор. Пусть BX.

Процедура; делит неотр. xисло X на 16

; в регистре BX - адрес X Результат записывается в X

DEL proc near

mov cl, 4

shr word ptr [bx], cl

ret

DEL endp

Основная программа; адрес числа в регистре BX

lea bx, A

call DEL

lea bx, B

call DEL

Задача.

Даны два массива однобайтовых чисел без знака. Найти сумму их максимальных элементов.

Опишем нахождение максимального элемента массива процедурой.

Формальными параметрами процедуры являются 1) начальный адрес массива

2) количество элементов массива

Будем передавать параметры через регистры. Адрес массива - через регистр BX

Кол-во элементов - через CX

Результат возвращаем основной программе через AL (эл-ты однобайтовые).

Masm

model small

. stack 128

. data

n equ 10

m equ 15

mas1 db n dup (? )

mas2 db m dup (? )

; процедура находит максимальный элемент массива

; адрес массива находится в регистре BX

; кол-во элементов - в CX

; результат возвращаем через AL

max proc

mov al, 0;

sled: cmp [bx], al

jle new

mov al, [bx]

new: inc bx

loop sled

ret

max endp

start: . . .

lea bx, mas1

mov cx, n

call max; результат в al

mov dl, al

lea bx, mas2

mov cx, m

call max

add dl, al

. . .

end start

 

Передача параметров через стек

 

Суть: осн. программа записывает параметры в стек, а процедура извлекает их оттуда.

!!! Результат лучше передавать через регистр. Иначе нужно осторожно чистить стек при выходе из процедуры.

Перед обращением к процедуре основная программа должна поместить в стек фактические параметры. Модель передачи (т. е. в каком порядке) определяет программист.

; основная программа

. . .

push par1

push par2

. . .

push parN

call name

Замечание! При вызове процедуры в стек запишется адрес возврата, т. о. параметры будут находиться «под» ним.

Процедура должна получить доступ к этим элементам. Т. к. они не на вершине стека, будем пользоваться регистром BP.

Замечание! Регистр BP может использоваться в основной программе, поэтому предварительно следует запомнить его старое значение. Лучше тоже в стеке, но надо будет это учитывать!

Таким образом, обратиться к первому (сверху) (а на самом деле это N параметр списку) элементу стека можно [BP+4], к следующему - [BP+6] и т. д.

!!! Если процедура является дальней, то следует еще +2, т. к. для адреса возврата поместят два элемента в стек (CS, IP). Для доступа к параметру N - [BP+6] и т. д.

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

Необходимо 1) поместить на вершину стека адрес возврата из процедуры

2) очистить стек от параметров (они уже стали ненужными)

(мы к ним только обращались, но не удаляли из стека)

1) mov SP, BP; восстановить SP

pop BP; восстановление старого BP

Теперь на вершине стека адрес возврата и можем выполнять RET

2) Очистить стек от параметров может основная программа ADD SP, 2*N

Но если много обращений к процедуре, то в осн. Программе будет много таких команд. Поэтому лучше очищать стек в самой процедуре.

Можно воспользоваться параметром [число] команды RET.

Этот параметр задает число байтов, на которое очищается стек.

Замечание! Адрес возврата не учитывать, т. к. Ret считывает его до очистки стека.

Таким образом необходимо выполнить возврат из процедуры RET 2*N

Итак, схема процедуры

name proc

push bp

mov bp, sp

. . . ; mov ax, [bp+4]; [bp+6], . . .

mov sp, bp

pop bp

ret 2*n

                              name endp

 

Поделиться:





Воспользуйтесь поиском по сайту:



©2015 - 2024 megalektsii.ru Все авторские права принадлежат авторам лекционных материалов. Обратная связь с нами...