Подпрограммы вызываются из основной программы по. Подпрограммы (процедуры и функции). В переменную S заносим большее из двух чисел А и В

понятие подпрограмм процедуры и функции

В ТУРБО ПАСКАЛЕ различают два вида подпрограмм - это процедуры и функции . Процедура и функция - это именованная последовательность описаний и операторов. Так же использование процедур и функций необходимо тогда, когда имеется возможность использовать некоторые фрагменты уже разработанных ранее алгоритмов. Кроме того, подпрограммы применяются для разбиения крупных программ на отдельные смысловые части в соответствии с модульным принципом в программировании. Процедура - это независимая именованная часть программы, которую можно вызвать по имени для выполнения определённой в ней последовательности действий. Процедуры служат для задания совокупности действий, направленных на изменение внешней по отношению к ним программной обстановки. В ПАСКАЛЬ, существуют стандартные процедуры: read, readln, write, writeln. Таким образом, концепция процедуры расширяет понятие оператора в языке ПАСКАЛЬ. Функция отличается от процедуры тем, что возвращает результат указанного при её описании типа. Вызов функции может осуществляться из выражения, где имя функции используется в качестве оператора. Функции являются частным случаем процедур, и обязательно возвращают в точку вызова результат как значение имени этой функции. При использовании функций необходимо учитывать совместимость типов в выражениях. В ПАСКАЛЬ, существует ряд стандартных функций, известных любой программе: sin, cos, eof, а также и другие функции из таблицы №3. Таким образом, концепция функции расширяет понятие выражения в языке ПАСКАЛЯ.

Локальные и глобальные переменные

Напомним, что каждый модуль (процедура, функция, программа) состоит из заголовка (procedure…, function…, program… ) и блока.

Если блок какой-либо процедуры p1 содержит внутри процедуру p2, то говорят, что p2 вложена в p1.

procedure p1(x: real; var y: real);

procedure p2(var z: real);

…………………….

…………………….

Любые идентификаторы, введенные внутри какого-либо блока (процедуры, функции) для описания переменных, констант, типов, процедур, называются локальными для данного блока. Такой блок вместе с вложенными в него модулями называют областью действия этих локальных переменных, констант, типов и процедур.

var y1, y2: real;

var a, b, c, d: real;

{ Переменные a, b, c, d являются локальными для sq1,

область их действия – процедура sq1 }

……………………………………

{ Переменные y1, y2 - нелокальные для sq1,

область их действия – t1 и sq1 }

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

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

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

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

Листинг 1. Пример подпрограммы - процедуры

G_Per=56 ; PeredatByte(); ... G_Per=37 ; PeredatByte(); ... /********************************************** Подпрограмма передачи байта через последовательный порт **********************************************/ void PeredatByte(void ) {do ;while (TI==0) SBUF=G_Per //то передать очередной байт }

Часто подпрограмма должна выполнять действия над каким то числом, значение которого неизвестно в момент написания программы. Это число можно передать через глобальную переменную как показано на рисунке 1. Однако намного удобнее использовать подпрограмму с параметрами . На языке высокого уровня вызов такой подпрограммы будет выглядеть следующим образом:

PeredatByte(56 ); ... PeredatByte(57 ); ... void PeredatByte(char byte) {do ;while (TI==0 ) //Если предыдущий байт передан SBUF=byte; //то передать очередной байт }

Сравните с программой, приведённой в листинге 1. Как по вашему, какая из программ более наглядная? В подпрограмму можно передавать и значительные объёмы данных, как например:

PeredatStroky("Напечатать строку");

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

Подпрограммы функции

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

Y=sin(x);

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

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

Листинг 2. Пример подпрограммы - процедуры с передачей данных через параметр подпрограммы

... if (PeredatByte("A" )!=0 )printf(“Отказ последовательного порта” ); ... /******************************************************* Подпрограмма передачи байта через последовательный порт ********************************************************/ int PeredatByte(char byte) {for (i=1000 ;i0;) {if (TI==0 ) //Если предыдущий байт передан {SBUF=byte; //то передать очередной байт return(0 ); //и вернуть признак нормального выполнения подпрограммы } } return(-1 ); //Если превышено время ожидания, то вернуть признак отказа передатчика }

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

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


Подпрограмма — это фрагмент кода, который имеет свое имя и создается в случае необходимости выполнять этот код несколько (много) раз. Подпрограмма описывается единожды перед началом основной программы (до begin). Компилятор пропускает данный фрагмент кода, пока в основной программе не встретит «вызов» подпрограммы, который выглядит как обращение к ней по имени (возможно, имени с аргументами, указанными в скобках).

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

Итак, рассмотрим синтаксис объявления и описания процедуры в Паскале

var …;{область объявления глобальных переменных} procedure название (параметры); {начало процедуры} var …;{объявление локальных переменных} begin … {тело процедуры} end;{конец процедуры} begin … {основная программа} end.

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

procedure pr; var i:integer; begin for i:=1 to 60 do begin {тело подпрограммы} write("*"); writeln; end; end; {конец подпрограммы} begin pr; {вызов процедуры} end.

В данном примере работы с процедурой в Паскале очевидно, что компилятор пропустит блок описания процедуры и дойдет до основной программы (9 строка кода). И только после того, как встретится вызов процедуры (10 строка), компилятор перейдет к ее выполнению, вернувшись к строке 1.

Процедуры с параметрами. Фактические и формальные параметры

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

Пример:
Построить фигуру

Особенность: Три похожие фигуры.

  • общее: размеры, угол поворота
  • отличия: координаты, цвет
  • Алгоритм решения:

    • выделить одинаковые или похожие действия (три фигуры);
    • найти в них общее (размеры, форма, угол поворота) и отличия (координаты, цвет);
    • отличия записать в виде неизвестных переменных, они будут параметрами процедуры.

    Решение на паскале:
    Процедура:

    Программа:

    15 uses GraphABC; procedure Tr( x, y: integer ; color: system. Drawing . Color ) ; begin MoveTo(x, y) ; LineTo(x, y- 60 ) ; LineTo(x+ 100 , y) ; LineTo(x, y) ; FloodFill(x+ 20 , y- 20 , color) ; end ; begin SetPenColor(clBlack) ; Tr(100 , 100 , clBlue) ; Tr(200 , 100 , clGreen) ; Tr(200 , 160 , clRed) ; end .

    uses GraphABC; procedure Tr(x, y: integer; color:system.Drawing.Color); begin MoveTo(x, y); LineTo(x, y-60); LineTo(x+100, y); LineTo(x, y); FloodFill(x+20, y-20,color); end; begin SetPenColor(clBlack); Tr(100, 100, clBlue); Tr(200, 100, clGreen); Tr(200, 160, clRed); end.

    Рассмотрим синтаксис объявления и описания процедуры с параметрами в Паскале.

    var …;{область объявления глобальных переменных} procedure pr(параметр1, параметр2: integer; параметр3:char); {начало процедуры} var …;{объявление локальных переменных} begin … {тело процедуры} end;{конец процедуры} begin … {основная программа} pr (параметр1, параметр2, параметр3); {вызов процедуры} end.

    Задание procedure 1: N вертикальных линий. N задается параметром процедуры.


    Задание procedure 2: Написать процедуру рисования N окружностей, сдвинутых по горизонтали. N , радиус R и отступ O задаются параметрами процедуры (всего 3 параметра).

    Пример: Написать процедуру, которая печатает 60 раз указанный символ (введенный с клавиатуры), каждый с новой строки

    Параметры процедуры (в некоторых языках они называются аргументами) указываются в скобках после ее имени (в объявлении).

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

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

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 var s: char ; procedure pr(a: char ) ; {a - формальный параметр} var i: integer ; begin for i: = 1 to 60 do begin write (a) ; writeln ; end ; end ; begin writeln ("simvol" ) ; readln (s) ; pr(s) ; {s - фактический параметр} end .

    var s:char; procedure pr(a:char); {a - формальный параметр} var i:integer; begin for i:=1 to 60 do begin write(a); writeln; end; end; begin writeln("simvol"); readln(s); pr(s); {s - фактический параметр} end.

    В данном примере при вызове процедуры компилятор заменит формальный параметр a фактическим параметром s , т.е. тем символом, который будет введен с клавиатуры. Оба параметра имеют тип данных char .

    Задача procedure 3. Написать процедуру, которая складывает два любых числа (два параметра).

    Процедуры с параметрами. Параметр-переменная

    1. способ:
    2. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 var x, y, m, n: integer ; procedure MaxNumber(a, b: integer ; var max: integer ) ; {a и b - параметры значения, max - параметр-переменная} begin if a>b then max: = a else max: = b; end ; begin write ("vvedite x,y" ) ; readln (x, y) ; MaxNumber(x, y, m) ; {фактические параметры} writeln ("max=" , m) end .

      var x,y,m,n:integer; procedure MaxNumber(a,b:integer;var max:integer); {a и b - параметры значения, max - параметр-переменная} begin if a>b then max:=a else max:=b; end; begin write("vvedite x,y"); readln(x,y); MaxNumber(x,y,m); {фактические параметры} writeln("max=",m) end.

      В примере формальные параметры a и b служат для помещения в них сравниваемых чисел, а параметр-переменная max — для сохранения в ней максимального из двух чисел. Параметр-переменная или выходной параметр передает свое значение в основную программу (фактическому параметру m), т.е. возвращает значение, тогда как формальные параметры-значения (входной параметр), наоборот, принимают значения из основной программы (из фактических параметров x и y). Для параметра-переменной (max) используются те ячейки памяти, которые отведены под соответствующий параметр при вызове процедуры (ячейка m).

      Таким образом, сформулируем понятия:

      Если в качестве формального параметра указана обычная переменная с указанием ее типа, то такой параметр есть параметр-значение или входной параметр (a и b в примере). Тип данных формального параметра-значения должен соответствовать типу данных его фактического параметра (a и b должны попарно соответствовать типу данных x и y).

      Если перед именем формального параметра в объявлении процедуры стоит служебное слово var , то такой параметр называется параметром-переменной или выходным параметром (max в примере). Для него используются те ячейки памяти, которые отведены под соответствующий параметр при вызове процедуры (m). Фактический параметр, соответствующий параметру-переменной, может быть только переменной (не константой, не литералом и не выражением).

    3. способ:
    4. Использование параметров-переменных позволяет тратить меньше памяти

      1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 var x, y: integer ; procedure exchange(a: integer ; var b: integer ) ; {b - параметр-переменная или выходной параметр} var c: integer ; begin if a>b then begin c: = a; a: = b; b: = c; {второй параметр процедуры - b - всегда будет максимальным} end ; end ; begin writeln ("введите два числа" ) ; readln (x, y) ; exchange (x, y) ; writeln ("max=" , y) end .

      var x,y:integer; procedure exchange(a: integer;var b:integer); {b - параметр-переменная или выходной параметр} var c:integer; begin if a>b then begin c:=a; a:=b; b:=c; {второй параметр процедуры - b - всегда будет максимальным} end; end; begin writeln("введите два числа"); readln(x,y); exchange (x,y); writeln("max=",y) end.

      Используя данный способ решения задачи, мы обошлись без третьего параметра. Для этого в процедуре мы использовали еще одну локальную переменную c . Процедура меняет значения переменных a и b таким образом, чтобы b всегда была максимальной. Поэтому в 15 строке программы в качестве максимальной выводится второй параметр (y), соответствующий формальному параметру b .

    Задача procedure 4.

    1. Необходимо определить наибольший общий делитель двух введенных чисел, используя цикл.
    2. Необходимо определить наибольший общий делитель двух введенных чисел, используя процедуру (два параметра-значения, один параметр-переменная).


    Словесный алгоритм:

    • Вводятся a и b (например, 18 и 24 )
    • В цикле повторяем действия:
    • Если а < b , то меняем переменные местами (1 шаг: a=24 , b=18 ; 2 шаг: a=18 , b=6 )
    • Переменной a присваиваем остаток от деления a на b (1 шаг: a=6 , b=18 ; 2 шаг: a=0 , b=6 )
    • Когда остаток равен 0 , выводится результат (значения переменной b ) (b=6 )

    Алгоритм решения поиска НОД:

    Задача procedure 5. Используя процедуры, построить фигуру:


    Задача procedure 6. Даны 3 различных массива целых чисел (размер каждого 15 элементов). В каждом массиве найти и среднее арифметическое значение.
    Для формирования элементов массива и подсчета суммы и среднего арифметического использовать одну процедуру (среднее арифметическое и сумму оформить как параметры-переменные).

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

    Пример: Создать процедуру для вывода десяти элементов массива (два параметра: количество элементов, массив)

    Показать решение:

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 const n = 10 ; var i: integer ; a, b: array [ 1 .. n ] of integer ; procedure arr_out (k: integer ; arr: array [ 1 .. n ] of integer ) ; var i: byte ; begin write ("вывод массива: " ) ; for i : = 1 to k do write (arr[ i] : 4 ) ; writeln ; end ; begin for i: = 1 to n do a[ i] : = random(10 ) ; arr_out (n, a) ; end .

    const n = 10; var i:integer; a, b: array of integer; procedure arr_out (k:integer; arr: array of integer); var i: byte; begin write ("вывод массива: "); for i:= 1 to k do write (arr[i]:4); writeln; end; begin for i:=1 to n do a[i]:=random(10); arr_out (n, a); end. integer ; procedure arr_rand (k: integer ; var arr: array [ 1 .. n ] of integer ) ; var i: byte ; begin write ("Заполнение массива случайными числами " ) ; randomize; for i : = 1 to k do arr[ i] : = random(100 ) ; end ; begin arr_rand (n, a) ; end .

    const n = 10; var a, b: array of integer; procedure arr_rand (k:integer; var arr: array of integer); var i: byte; begin write ("Заполнение массива случайными числами "); randomize; for i:= 1 to k do arr[i]:=random(100); end; begin arr_rand (n, a); end.

    Задача procedure 7. Объедините обе решенные задачи (для заполнения и вывода массива).


    Задача procedure 8. Добавьте в задачу процедуру для заполнения значений массива пользователем (ввод значений с клавиатуры). Оформите вывод двух разных массивов: один — со значениями, сформированными случайным образом, другой — со значениями, введенными пользователем.


    Задача procedure 9. Составить программу с процедурой для вычисления (входные параметры: число и степень). Для вывода результата использовать параметр-переменную.

    Самостоятельная работа

    1 вариант: для 5 одномерных массивов определять произведение элементов каждого массива, используя процедуру с двумя параметрами - число элементов массива и параметр-переменная для вывода произведения.

    2 вариант: для 5 одномерных массивов определять минимальный элемент каждого массива, используя процедуру с двумя параметрами - число элементов массива и параметр-переменная для вывода минимального элемента.

    * сложное С помощью процедуры формировать случайным образом одномерные массивы из 10 элементов (значения от -20 до +20). Вызывать процедуру до тех пор, пока среди значений не появится ноль.

    Язык G- и М-кодов, как и любой другой язык программирования, позволяет работать с подпрограммами и совершать переходы. Посредством функции подпрограммы основная (главная) управляющая программа может вызывать из памяти другую программу (подпрограмму) и выполнить ее определенное число раз. Если УП содержит часто повторяемое действие или работает по определенному шаблону, то использование подпрограмм позволяет упростить программу обработки и сделать ее гораздо меньшей в размере.

    Существуют два вида подпрограмм – внутренние и внешние. Внутренние подпрограммы вызываются при помощи кода М97 и содержатся внутри главной программы. То есть они находятся в одном файле. Внешние подпрограммы вызываются кодом М98 и не содержатся в теле главной программы. В этом случае главная программа и подпрограмма находятся в разных файлах.

    Рис. 10.1. Схема внутренней подпрограммы

    Внутренняя подпрограмма выполняется, когда СЧПУ встречает код М97. При этом адрес Р указывает на номер кадра, к которому нужно перейти, то есть туда, где начинается внутренняя подпрограмма. Когда СЧПУ находит кадр с кодом окончания подпрограммы М99, то выполнение внутренней подпрограммызавершается и управление передается кадру главной программы, следующему за кадром, вызвавшим завершенную подпрограмму.


    Рис. 10.2. Схема внешней подпрограммы

    Внешние подпрограммы работают похожим образом. Когда в главной программе встречается кадр с кодом М98, то вызывается подпрограмма с номером, установленным при помощи Р-адреса. При нахождении кода М99 управление возвращается главной программе, то есть выполняется кадр главной программы, следующий за кадром с М98. Учтите, что внешняя подпрограмма находится в отдельном файле. По сути, внешняя подпрограмма – это отдельная программа с индивидуальным номером, которая при желании может быть выполнена независимо от главной программы. Для вызова подпрограммы необходимо, чтобы она находилась в памяти СЧПУ.

    Пример УП с внутренней подпрограммой:

    При помощи L-адреса определяется, сколько раз нужно вызвать ту или иную подпрограмму. Если подпрограмму нужно вызвать всего один раз, то L в кадре можно не указывать.

    М98 Р1000 L4 – подпрограмма будет вызвана 4 раза.

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

    Итак, на рис. 10.3 изображена деталь с 4 группами отверстий диаметром 3 мм. Нулевой точкой является верхний левый угол детали. Сначала создадим главную программу, которая будет позиционировать инструмент к каждой группе отверстий. Затем напишем подпрограмму, необходимую для сверления 4 отверстий в одной группе. Учтите, что в подпрограмме используются относительные координаты, а смена инструмента и основные команды находятся в главной программе.

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


    Обе эти программы необходимо передать в СЧПУ, оператор станка должен вызвать программу О0001 и запустить ее. Главная программа работает с абсолютными координатами и перемещает инструмент последовательно в центр каждой из четырех групп отверстий. Когда СЧПУ встречает кадр с М98 Р1000, то происходит передача управления подпрограмме с номером 01000. В этот момент инструмент уже находится в центре группы отверстий. Переключаемся в режим относительного (инкрементального) программирования и используем постоянный цикл сверления. После завершения сверления четырех отверстий одной группы выключаем цикл сверления командой G80 и переходим в режим абсолютных координат G90, для того чтобы правильно выполнить позиционирование в главной программе. Код М99 передает управление кадру главной программы, который следует за кадром, вызвавшим эту подпрограмму. Затем инструмент перемещается в центр следующей группы отверстий, и снова вызывается подпрограмма 01000. И так далее, пока не просверлим все отверстия и СЧПУ не прочтет код завершения программы М30.

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

    Из главной программы можно вызвать несколько различных подпрограмм. Более того, из каждой подпрограммы можно вызвать несколько других подпрограмм. Системы ЧПУ могут накладывать ограничения на вложенность и количество выполняемых подпрограмм, поэтому внимательно прочитайте документацию станка и стойки, прежде чем начнете работать с подпрограммами.

    Глава 17. Команды вызова процедур

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

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

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

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

    На рис. 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 - счетчик команд ядра процессора):

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

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

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

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

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

    Стеки

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

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

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

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


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

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

    Move NEWITEM.(SP),

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

    Эти две команды перемещают значение, хранившееся на вершине стека, в дру­гое место оперативной памяти, по адресу 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 выполняет операцию - и устанавливает флаги условий в соответствии с полученным результатом. Она не изменяет значения ни одного из операндов. Фрагмент а таблицы 17.1 демонстрирует выталкивание элемента из стека, а фрагмент б этой же таблицы - помещение элемента в стек при реализации контроля пустого и полного стека при выполнении операций проталкивания и выталкивания элемента.

    Таблица 17.1

    Метка Команда Операнды