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



Что такое подпрограмма

 

Подпрограмма представляет собой самостоятельную компьютерную программу, включенную в состав более сложной программы (по отношению к подпрограмме она выступает в роли основной или вызывающей программы). В любом месте основной программы можно обратиться к подпрограмме (чаще говорят "вызвать подпрограмму"). Ядро процессора при этом должно перейти на ад­рес первой команды подпрограммы, а после ее завершения вернуться на адрес ко­манды основной программы, следующей за командой вызова подпрограммы.

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

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

На рис. 17.1а показан пример использования подпрограммы. В данном примере имеется основная программа, которая разме­щена в оперативной памяти, начиная с адреса 4000. В основной программе существует вызов подпрограммы PROC1, которая размещена в оперативной памяти, начиная с адреса 4500. Когда в процессе выполнения основной программы ядро процессора дойдет до этой команды, оно прервет выполнение основной программы и перейдет на выполнение подпро­граммы PROC1, поместив ее начальный адрес 4500 в счетчик команд. В теле под­программы PROC1 есть две команды вызова подпрограммы PROC2, которая разме­щена в оперативной памяти, начиная с адреса 4800. Дойдя до каждой из этих команд, ядро процес­сора прекратит выполнять подпрограмму PROC1 и перейдет на выполнение подпрограммы PROC2. Встретив в подпрограмме команду RETURN, ядро процессора вер­нется в вызывающую программу и продолжит ее выполнение с команды, следую­щей за командой CALL, которая вызвала переход на только что завершенную под­программу. Этот процесс схематически показан на рис. 17.1.6.

      Необходимо обратить внимание на следующие моменты:

подпрограмма может быть вызвана из любого места других программных
модулей. Таких вызовов может быть сколько угодно.

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

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

 

 

 


 

 

 


Рис. 17.1. Вложенный вызов подпрограмм:

 а — команды вызова и возврата; б — последовательность выполнения команд

 

     Из всего этого следует, что ядро процессора при выполнении команды вызова подпрограммы должно каким-то образом сохранить адрес возврата (т.е. адрес команды вызывающей программы, следующей за выполняемой командой вызо­ва). Существует три места, где можно было бы сохранить адрес возврата:

регистр процессора;

начальный адрес подпрограммы;

верхняя ячейка стека.

Рассмотрим машинную команду CALL X, которая интерпретируется как "вызов подпрограммы, расположенной по адресу X". Если для хранения адреса возврата использовать регистр Rn, то выполнение команды CALL X должно про­ходить следующим образом (PC — счетчик команд ядра процессора):

 

Rn   PC + D

 

PC   X

 

В этой записи D — длина текущей команды. Затем адрес возврата оказывается в регистре Rn, откуда его может извлечь вызванная подпрограмма и сохранить где-либо в оперативной памяти для выполнения в дальнейшем возврата в вызывающую программу.

Если будет принято решение сохранять адрес возврата по начальному адре­су вызываемой подпрограммы, то при выполнении команды CALL X ядру процессора нужно будет выполнять следующие операции:

 

(X)   PC + D

 

 PC   X + 1

 

Это довольно удобно, поскольку адрес возврата всегда сохраняется в месте, точно известном подпрограмме (точнее, ее разработчику).

Оба описанных подхода работоспособны и используются на практике. Единственным, но довольно существенным их недостатком является невозможность реализации реентерабельныхподпрограмм. Реентерабельность подпрограммы означает, что она может быть вызвана повторно еще до завершения текущего вызова. Например, это происходит, если внутри подпрограммы вызывается дру­гая подпрограмма, которая, в свою очередь, вызывает первую. Реентерабельны­ми должны быть и подпрограммы, реализующие рекурсивные алгоритмы.

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

 

 

Стеки

 

Стек — это список элементов данных, обычно слов или байтов, доступ к которым ограничен следующим правилом: элементы этого списка могут добавляться толь­ко в его конец и удаляться только из конца. Конец списка называется вершиной стека, а его начало — дном. Такую структуру иногда называют магазином. Пред­ставьте себе стопку подносов в столовой. Клиенты берут подносы сверху, работ­ники столовой, добавляя чистые подносы, тоже кладут их на верх стопки. Этот механизм хранения хорошо описывается емкой фразой «последним вошел — пер­вым вышел» (Last In First Out, LIFO), означающей, что элемент данных, помещенный в стек последним, удаляется из него первым. Операцию помещения но­вого элемента в стек часто называют его проталкиванием (push), а операцию извлечения последнего элемента из стека называют его выталкиванием (pop).

Хранящиеся в оперативной памяти компьютера данные могут быть организованы в виде сте­ка, так чтобы последовательные элементы располагались друг за другом. Предположим, что первый элемент хранится по адресу BOTTOM, а когда в стек помещаются новые элементы, они располагаются в порядке уменьшения последовательных адресов. Таким образом, стек растет в направлении уменьшения адресов, что является весьма распространенной практикой.

На рис. 17.2 показано, как располагается в памяти компьютера стек, элементы которого занимают по одному слову.

На «дне» он содержит числовое значение 43, а на вершине — 28. Для отслеживания адреса вершины стека используется регистр ядра процессора, называемый указателем стека (Stack Pointer, SP). Это может быть один из регистров общего назначения или же регистр, специально предназначен­ный для этой цели.

 

 

 


Рис. 17.2. Стек слов в оперативной памяти

 

Если предположить, что оперативная память адресуется побайтно и слово имеет длину 32 разряда, операцию проталкивания в стек можно реализовать так:

 

                                 Subtract #4,SP

                                             Move    NEWITEM.(SP),

 

где команда Subtract вычитает исходный операнд 4 из результирующего операн­да, содержащегося в регистре SP, и помещает результат в регистр SP. Эти две ко­манды помещают слово, хранящееся по адресу NEWiTEM, на вершину стека, предварительно уменьшая указатель стека на 4. Операция выталкивания из стека может быть реализована так:

 

                                           Move (SP),ITEM

                                       Add #4,SP  .

Эти две команды перемещают значение, хранившееся на вершине стека, в дру­гое место оперативной памяти, по адресу ITEM, а затем уменьшают указатель стека на 4, чтобы он указывал на тот элемент, который теперь располагается на вершине стека. Результат выполнения каждой из этих операций для стека, показанного на рис. 17. 2, приведен на рис. 17.3.

 

 

 


Рис. 17.3. Результат выполнения операций со стеком

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

 Move NEWITEM,-(SP),

а выталкивание элемента из стека можно выполнить посредством команды

 Move (SP)+,ITEM.

Когда стек используется программой, для него обычно выделяется фиксирован­ное количество ячеек оперативной памяти. В этом случае нужно проследить за тем, чтобы программа не пыталась помещать новые элементы в стек, достигший своего максимального размера. Кроме того, она не должна пытаться вытолкнуть элемент из пустого стека, что могло бы произойти в случае логической ошибки.

Предположим, что стек заполняется, начиная с адреса 2000 (BOTTOM) до 1500 и далее. Первоначально в регистр, играющий роль указателя стека, загружа­ется значение 2004. Напомним, что перед помещением в стек нового элемента данных из значения SP каждый раз вычитается 4. Поэтому начальное значение 2004 означает, что первый элемент стека будет иметь адрес 2000. Для предотвра­щения попыток помещения элемента в полный стек или удаления элемента из пустого стека нужно несколько усложнить реализацию операций проталкивания и выталкивания элемента. Для выполнения каждой из этих операций указаннойвыше команды недостаточно — ее нужно заменить последовательностью команд, приведенной в таблице 17.1.

Команда сравнения Compare src,dst выполняет операцию [src]-[dst] и устанавливает флаги условий в соответствии с полученным результатом. Она не изменяет значения ни одного из операндов.  Фрагмент а таблицы 17.1 демонстрирует выталкивание элемента из стека, а фрагмент б этой же таблицы -  помещение элемента в стек при реализации  контроля пустого и полного стека при выполнении операций проталкивания и выталкивания элемента.

 

Таблица 17.1

Метка

Команда

Операнды Комментарий

Фрагмент а

SAFEPOP

Compare #2000,SP

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

 

Branch >0 EMPTYERROR

 

Move (SP)+, ITEM В противном случае элемент, расположенный на вершине стека выталкивается, а затем помещается в память по адресу ITEM

Фрагмент б

SAFEPUSH

Compare

Branch ≤0

#1500,SP  FULLERROR Проверка того, не содержит ли указатель стека значение меньше или равное 1500. Если содержит, значит стек полон. В таком случае выполняется переход к подпрограмме FULLERROR, выполняющей соответствующее действие

Move

NEWITEM, -(SP) В противном случае в стек помещается элемент, хранящийся в памяти по адресу NEWITEM
         

 

 

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

 

Вызывая подпрограмму, программа должна передать ей параметры (операнды),  или же их адреса. Закончив свою работу, подпрограмма вернет другие параметры — результаты выполнения. Такой обмен информацией между вызывающей программой и подпрограммой называ­ется передачей параметров. Передача параметров может выполняться нескольки­ми способами. Например, параметры можно помещать в регистры или в оперативную память, от­куда подпрограмма сможет их считать. В качестве альтернативы параметры можно поместить в стек, используемый для хранения адресов возврата. 

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

 

Таблица 17.2

Метка

Команда

Операнды Комментарий

Вызывающая программа

 

Move N, R1 R1 играет роль счетчика
Move #NUM,R2 R2 указывает на последовательность
Call LISTADD Вызов подпрограммы
Move R0,SUM Сохранение результата

 

……..    

Подпрограмма

LISTADD

 

LOOP

Clear

R0 Инициализация суммы значением 0

Add

(R2)+,R0 Добавление числа из последовательности

Decrement

R1 Уменьшение счетчика

Branch>0

LOOP Если счетчик не равен 0, возврат на цикл

Return

  Возврат в вызывающую программу
         

 

Длина последовательности п, информация о которой хранится в оперативной памяти по адресу N, и адрес первого числа NUM1 передаются под­программе через регистры R1 и R2. Вычисленная подпрограммой сумма возвра­щается вызывающей программе через регистр R0. Первые две команды загружают в регистры R1 и R2 значения N и NUM1. Команда CALL выполняет переход к подпрограмме, начинающейся по ад­ресу LISTADD. Кроме того, эта команда помещает в стек адрес воз­врата из подпрограммы. Подпрограмма вычисляет сумму и помещает ее в регистр R0. После возврата из подпрограммы вызывающая программа сохраняет эту сум­му в оперативной памяти по адресу SUM.

Если у подпрограммы много параметров, для их передачи может просто не хватить регистров общего назначения. С другой стороны, стек — структура гиб­кая, в него можно поместить много параметров. Следующий пример показывает, как выполняется передача параметров через стек. В таблице 17.3приведена та же программа, выполняющая сложение последовательности чисел, но реализованная в виде подпрограммы LISTADD. Любая другая программа может вызвать указанную подпрограмму для сложения последовательности чисел. Подпрограмме LISTADD передается адрес первого числа в последовательности и количество ее элементов. Подпрограмма выполня­ет сложение и возвращает полученную сумму. Ее параметры помещаются в стек процессора, на который указывает регистр SP. Предположим, что до вызова под­программы вершина стека располагается на уровне 1 (рис.17.4). Вызывающая программа помещает в стек адрес NUM1 и значение п и вызывает подпрограмму LISTADD. Кроме того, команда CALL помещает в стек адрес возврата из подпро­граммы. Теперь вершина стека располагается на уровне 2.

Таблица 17.3

Метка

Команда

Операнды Комментарий

Предполагается, что вершина стека расположена на уровне 1

 

Move #NUM1,-(SP) Помещение параметров в стек

 

Move N,-(SP)  

 

Call LISTADD Вызов подпрограммы ( вершина стека на уровне 2)

 

Move 4(SP),SUM Сохранение результата

 

Add #8,SP Восстановление вершины стека ( вершина стека на уровне 1)

 

…….    

LISTADD

MoveMultyple R0-R2,-(SP) Сохранение регистров ( вершина стека на уровне 3)

 

Move 16(SP),R1 Инициализация счетчика значением n

 

Move 20(SP),R2 Инициализация указателя на последовательность чисел

 

Clear R0 Инициализация суммы значением 0

LOOP

Add (R2)+,R0 Добавление числа из последовательности чисел

 

Decrement R1 Уменьшение счетчика

 

Branch>0 LOOP Проверка цикла

 

Move R0,20(SP) Помещение результата в стек

 

MoveMultyple (SP)+,R0-R2 Восстановление регистров

 

Return   Возврат в вызывающую программу
         

 

 

 

 

 


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

 

Подпрограмма использует три регистра. Поскольку в них могут содержаться данные, принадлежащие вызывающей программе, их содержимое сохраняется в стеке. Для помещения в стек содержимого регистров от R0 до R2 использована команда MoveMultiple. Подобные команды имеются у многих архитектур. Те­перь вершина стека располагается на уровне 3. С помощью индексной адресации подпрограмма считывает из стека параметры п и NUM1. Значение указателя сте­ка при этом не меняется, поскольку на вершине стека по-прежнему располагают­ся нужные элементы данных. Значение п загружается в регистр R1, который будет играть роль счетчика, а адрес NUM1 — в регистр R2, который будет слу­жить указателем при сканировании списка значений. Регистр R0 должен содер­жать результат суммирования. Перед возвратом из подпрограммы содержимое ре­гистра R0 помещается в стек, заменяя параметр NUM1, который больше не нужен. Затем из стека восстанавливается содержимое трех регистров, использовав­шихся подпрограммой. Теперь верхним элементом стека является адрес возврата, расположенный на уровне 2. После возврата из подпрограммы вызывающая программа сохраняет результат в оперативной памяти по адресу SUM и возвращает вершину сте­ка на ее исходный уровень, уменьшая значение SP на 8.

 

 


Дата добавления: 2021-03-18; просмотров: 146; Мы поможем в написании вашей работы!

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






Мы поможем в написании ваших работ!