Учебное пособие по дисциплине Разработка языков программирования высокого уровня
Скачать 1.74 Mb.
|
4.2. Структура подпрограмм и их типыВыше указывалось, что подпрограмма - это поименованный блок, оформленный специальным образом. Причем, структура подпрограммы весьма схожа со структурой основной программы. Из рисунка 4.2, на котором изображена структура программы на языке Pascal, отчетливо видно, что подпрограмма содержит те же разделы, что и главная программа. Разница только в синтаксисе оператора заголовка. Оператор заголовка подпрограммы, помимо имени, должен содержать тип подпрограммы и список параметров, через которые осуществляется связь по данным между подпрограммой и вызывающей программой. По типу подпрограммы делятся на: процедуры - подпрограммы, которые могут иметь сколько угодно параметров или не иметь их совсем, при этом активизация процедуры происходит посредством самостоятельного оператора; функции - подпрограммы, имеющие сколько угодно входных параметров, но только один выходной, который является обязательным и содержится в ячейке, идентифицируемой именем функции. Поэтому вызов функции осуществляется только в арифметическом выражении оператора присваивания или в списке оператора вывода. PROGRAM <имя программы> Заголовок основной программы; {Раздел описаний} Uses Подключаемые библиотеки; Label Описание глобальных меток; Const Описание глобальных констант; Туре Описание глобальных типов; Var Описание глобальных переменных; Procedure (Funktion) <имя> [(<список параметров>)] Заголовок блока (процедуры или функции); {Раздел локальных описаний } Label Описание локальных меток; Const Описание локальных констант; Туре Описание локальных типов; Var Описание локальных переменных; begin {тело подпрограммы (процедуры или функции)} end; BEGIN { Раздел операторов - тело программы} END. Рисунок 4.2. Структура программы на языке Pascal В некоторых языках, например. С, C++ и подобных нет четкой иерархии между главной программой и подпрограммами, структуризации областей видимости внутри программных единиц, разделения на процедуры и функции. Программа на С представляет собой набор функций, хотя среди них обязательно должна быть функция main() - аналог главной программы. При этом функция может иметь как свойства procedure, так и function в понимании Pascal. Такие качества дают гибкость при программировании, но, безусловно, уменьшают надежность программ, написанных на С-подобных языках, их читабельность. 4.3. Дескриптор подпрограммыС целью осуществления корректного подключения подпрограммы операционная система должна реализовать определенные механизмы. Их два: 1. Механизм обеспечения передачи управления подпрограмме при ее вызове, 2. Механизм обеспечения возврата управления в вызывающую программу. Механизм передачи управления в подпрограмму при ее вызове осуществляет следующие действия: - сохранение текущего состояния памяти вызывающего ее модуля; - передачу параметров в подпрограмму; - сохранение адреса возврата в вызывающую программу. Механизм возврата управления из подпрограммы в вызывающую программу заключается в следующем: присваивание вычисленных значений выходным параметрам подпрограммы; восстановление текущего состояния вызывающего модуля; передача управления по адресу возврата в вызывающий модуль. Выполнение описанных механизмов обеспечиваются с помощью специального дескриптора, который называется запись активации подпрограммы и формируется в автоматической памяти (стеке). Экземпляр записи активации (ЭЗА) под каждую подпрограмму, формируется при ее активации и разрушается в момент завершения работы. На рисунке 4.3 приведена структура ЭЗА при активных главной программе main, подпрограммах f1 и f2 в ситуации статического обзора данных. Особенности реализации подпрограмм: - каждая подпрограмма имеет, по крайней мере, один вход и выход, хотя можно организовать процедуру, которая осуществляет выполнение алгоритма пользуясь только глобальными и локальными переменными и не будет иметь входных и выходных параметров, - на время выполнения подпрограммы выполнение вызывающего ее модуля приостанавливается, - В каждый момент времени выполняется только одна подпрограмма (разумеется, если задействован последовательный вычислитель, а на высокопроизводительной архитектуре со многими процессорами можно организовать обработку процессов параллельно), - управление после выполнения подпрограммы всегда возвращается в вызывающий модуль. 4.4. Механизмы обмена данными с подпрограммой Итак, если мы оформили часть алгоритма в виде подпрограммы, то основная задача - передать информацию внутрь блока и “выбросить” результат работы в вызывающую программу для дальнейшего использования. Существует два способа информационного обмена между подпрограммой и вызывающей программой: 1. Прямой доступ к глобальным переменным. 2. Передача через параметры подпрограммы. Этот способ является более гибким, чем глобальные переменные. Подпрограмма может вычисляться много раз для любых новых фактических данных, а глобальные переменные надо переопределять перед каждым вызовом. Кроме того, глобальные переменные будут видны и в других подпрограммах. Механизм глобальных и локальных параметров Выше на рисунке 4.2. приведена структура подпрограммы, которая очень похожа на структуру основной программы: присутствуют те же два раздела - блок описаний локальных переменных и тела подпрограммы. Но в разделе описаний подпрограммы объявляемые объекты названы "локальными", в отличие от "глобальных" в разделе описаний основной программы. Дело здесь в следующем. Все имена, описанные внутри подпрограммы, локализуются в ней, они как бы невидимы извне подпрограммы. Таким образом, со стороны операторов, использующих обращение к подпрограмме, она трактуется как "черный ящик», в которой реализуется тот или иной алгоритм, действуют те или иные переменные, константы и т. п. Все имена в пределах подпрограммы, в которой они объявлены должны быть уникальными и не могут совпадать с именем подпрограммы. C другой стороны, объекты верхних уровней действуют во всех подпрограммах низших уровней. Образно говоря, любая подпрограмма как бы окружена полупрозрачными стенками: снаружи подпрограммы мы не видим внутренности, но, попав внутрь, можем наблюдать все, что вне ее. Пусть имеем такое описание (здесь для удобства термин «подпрограмма» заменяется на «блок»): Program . . ; var V1 : . . . ; Блок A; var V2 : . . . ; . . . . . . end {A}; Блок B; var V3 : . . . ; Блок B1; var V4 : . . . ; Блок B11; var V5 : . . . ; . . . . . . . . Из блока В11 доступны все пять переменных V1…V5, из блока B1 доступны VI…V4, и т.д., из основной программы - только V1. При этом переменные, описанные внутри блока, с точки зрения этого блока являются локальными. Переменные, описанные в блоках высших уровней или в главной программе, называются глобальными. Так, V1 является глобальной переменной для всех блоков. V2 - локальна для A. V3 - локальна для В и глобальна для В1 и В11. При этом реализуется механизм сокрытия переменных, рассмотренный в разделе 2.5. Он заключается в том, что локализованные в подпрограмме имена могут совпадать с ранее объявленными глобальными именами. В этом случае считается, что локальное имя «закрывает» глобальное и делает его недоступным. Таким образом, одноименные глобальные и локальные переменные - это разные переменные. При этом через глобальные переменные принято передавать информацию, которая не будет меняться при различных вызовах подпрограммы. Механизм параметров программы Если при каждом новом обращении к подпрограмме в нее следует передавать различные значения исходных данных, то принято делать это через специальные переменные, которые называются входными параметрами подпрограммы. Результаты работы подпрограммы для разных значений исходных данных будут, естественно, различны. Они передаются назад в вызывающую программу через переменные, которые называются выходными параметрами подпрограммы. Разумеется, выходные параметры должны заполняться данными внутри подпрограммы, а входные - в вызывающей, перед обращением к ней. Параметры подпрограммы, как входные, так и выходные, должны быть описаны в заголовке, здесь они называются формальными. Количество входных параметров неограничено. Если подпрограмма имеет много выходных параметров, то она называется процедурой. Заголовок процедуры имеет вид: Procedure <имя процедуры> [ ( <список формальных параметров> )]; B процедуре <список формальных параметров> необязателен и может отсутствовать. Если же он есть, то в нем должны быть перечислены имена формальных параметров и их тип. Ниже приведен пример в синтаксисе языка Pascal, в котором выходные параметры должны предваряться еще и ключевым словом var Procedure SB (a : real; b : integer; var c : char; var d : real); Если результатом исполнения операторов, образующих тело подпрограммы, является некоторое единственное значение того или иного типа, то такую подпрограмму можно оформить как функцию. Выходное значение для функции должно быть помещено в ячейку <имя функции>. Поэтому в заголовке функции присутствует описатель <тип>, который определяет тип переменной <имя функции>, а также тип возвращаемого функцией результата. Заголовок функции следующий (синтаксис Pascal): Function <имя функции> [ ( <список входных формальных операторов> ) ] : <тип>; Как видно из примеров, параметры в списке отделяются друг от друга точкой с запятой. Несколько следующих подряд однотипных параметров можно объединять в подсписки, например, заголовок функции можно написать проще: Function F (a, b : real) : real; Следует заметить, что при написании операторов тела подпрограммы совершенно на равных участвуют как формальные параметры, так и глобальные и локальные переменные. Обращение к блоку Итак, мы научились оформлять часть алгоритма в виде блоков, процедур или функций, которые размещаются в разделе описаний основной программы. Но написать текст подпрограммы еще не значит выполнить описанный в ней алгоритм. Для этого к блоку следует обратиться из тела программы. Существуют специальные операторы вызовов процедур и функций. Для процедур оператор вызова имеет формат: <имя процедуры> [ ( <список факт. параметров> ) ]; Здесь <список факт. параметров> - это список фактических параметров, построенный по следующему правилу. Вместо входных формальных параметров следует подставить либо числовые значения, либо имена глобальных переменных, содержащих эти значения. Делается это для того, чтобы "наполнить" операторы тела подпрограммы числовыми данными, иначе выполнение алгоритма будет невозможно. Вместо выходных формальных параметров следует: - либо оставить имя той же переменной, что в заголовке, - либо подставить имя другой глобальной переменной. Вызов функции носит иной характер. К функции нельзя обратиться по правилу процедуры, т.е. написать просто имя с фактическими параметрами. Имя функции - это еще и ячейка с результатом, поэтому иначе, как в правой части оператора присваивания или списка параметров оператора вывода имя функции встретиться не может, поэтому обращение к функции можно использовать в арифметических или логических выражениях наряду с переменными и константами. Замена формальных параметров на фактические (связывание формальных и фактических параметров) и для процедуры, и для функции осуществляться как: Позиционное, когда тип, количество и порядок следования фактических параметров строго соответствует типу, количеству и порядку следования формальных (языки Pascal, С и др.). С использованием ключевых параметров, когда при обращении в заголовке фактический параметр указывается вместе с формальным, который выступает в роли ключа (языки Ada, Fortran-90): SUMER (LENGTH => MY LENGTH, LIST => MY LIST) Использование ключевых параметров дает различные гибкие возможности (хотя и повышает вероятность ошибок), например, в языках C++, Fortran-90, Ada разработчики «разрешили» формальным параметрам иметь еще и значения по умолчанию, которые будут использованы, если не задано фактическое: function COM (IN: FLOAT; EX : INTEGER:=1; TAX : FLOAT) …… return Тогда вызов этой функции следующий (при этом после пропущенного параметра все остальные должны быть ключевыми): PAY:= COM(2000.0, TAX => 0.15); B C++ ключевых параметров нет, поэтому те, что по умолчанию должны быть в конце списка: function com (float in, float tax, integer ex =1); обращение pay = com (2000.0, 0.15); Кроме того языки С, С++ не следят за позиционированием параметров, т.е. соответствием количества формальных и фактических параметров. Это ответственность программиста, что, безусловно, снижает надежность языков. Параметры-массивы Часто возникает необходимость в подпрограмму как формальный параметр передать массив. Некоторые языки не позволяют это сделать напрямую, например, Pascal, не позволяет сделать следующее описание: Procedure S ( a : array[1…10] of real); Для преодоления указанной сложности разработчики языка Pascal ввели возможность описания нового нестандартного типа данных: Туре <имя типа> = <описатель типа>; Например, type vector = array [1..10] of real; Здесь vector имя нестандартного типа, который теперь существует в пользовательской программе наряду со стандартными real, integer, char и другими, и который можно использовать и при описании переменных в операторе var, и в заголовках процедур и функций: Program ; type vector = array [1..10] of real; var d : vector; Procedure S ( a : vector ); . . . . . . . . . . . Разработчики С-подобных языков нашли другой способ: массивы в качестве параметров функций передаются через указатели. 4.5. Режимы и модели передачи данных через параметры Передача данных между фактическим и формальным параметрами может происходить в 3-х режимах: Режим ввода (In) - формальный параметр только получает данные от фактического (только входные); Режим вывода (Out) - формальный параметр только передает данные фактическому (только выходные); Режим ввода-вывода (InOut) - формальный параметр может и получать, и передавать данные фактическому (быть и входным и выходным). Модели реализации режимов передачи данных с помощью механизма параметров Существует несколько моделей реализаций указанных режимов передачи параметров. Модель передачи по значению Реализует режим ввода и означает: реальное копирование значения фактического параметра в ячейку формального, который становится реальной локальной переменной. Надежный способ, но копирование может быть дорогим и по времени, и по памяти (требуется хранить 2 копии), если передаем большой массив. передача пути доступа к значению фактической переменной в вызывающей программе, и при этом фактическая переменная блокируется на запись (например, С++): можно только читать из нее. Защита от записи может оказаться сложным делом, особенно, если передавать параметр по иерархии подпрограмм. Модель передачи по результату Реализует режим вывода. Реализуется аналогично п.1.: копированием формальной переменной в фактическую или передачей пути доступа. При этом возможна следующая проблема. Пусть подпрограмма имеет выходной параметр list[i]. Возникает вопрос, когда вычислять адрес фактической переменной: во время вызова подпрограммы или при возвращении из нее. Если переменная і изменяется во время выполнения процедуры, то и адрес list[i] на входе в подпрограмму и на выходе из нее будет разный. Модель передачи по значению и по результату Режим ввода-вывода. Комбинация передач по значению и по результату. ту. Фактически при этом формальные параметры должны храниться в локальной области процедуры, и дважды делается копирование: в них и из них. Модель передачи по ссылке Реализация режима ввода-вывода, заключается в передачи пути доступа (адреса) фактической переменной в подпрограмму. Эффективна по времени и по объему памяти, поэтому находит реализацию во многих языках. Недостаток заключается в возможности совмещения имен (альтернативные имена). Ниже приведены примеры таких ситуаций. Пример 1. прототип void fun (int *first, int *second); обращение fun(&t, &t); т. е. имена first, second будут альтернативными. Пример 2. обращение fun (&list[i], &list[j]); если i, j равны, то опять возникает совмещение имен. Пример 3. Procedure big; Var global : integer; Procedure small(var local: integer); begin … end; Begin Small(global); End; Здесь возникает альтернативность между формальным параметром local Procedure small и глобальной для Procedure small переменной global. Модель передачи по имени Реализация режима ввода-вывода, но с особенностями. Особенности заключаются в следующем. Вид фактического параметра диктует выбор модели реализации. Если фактический параметр - скалярная величина, то передача равносильна передаче по ссылке. Если - константное выражение, то равносильна передаче по значению. Если фактический параметр выражение, содержащее переменную, то передача особая. Она характеризуется поздним связыванием, т.е. связывание формального параметра с фактическим произойдет не в момент вызова процедуры, а в момент присваивания формальному параметру конкретного значения или ссылки на него. Например, пусть фактический параметр - элемент массива, ясно, что индексное выражение может изменяться во время выполнения программы между моментами обращения к этому параметру и не один раз. Поэтому и связь должна быть в момент записи, динамической. Это позволяет использовать формальный параметр для обращения к разным элементам массива. Преимущества передачи по имени - гибкость. Недостаток заключается в медленном выполнении этого механизма. Справедливости ради, следует заметить, что такая модель в силу сложности и неэффективности была реализована лишь в языке АЛГОЛ-60. |