лекции (2003) (Глазкова) (1160821), страница 17
Текст из файла (страница 17)
Среды ссылок бывают:
-
локальные среды ссылок (все переменные, определенные в блоке процедуры, относятся к локальной среде ссылок);
-
глобальные среды ссылок (это объекты, которые определены на самом верхнем уровне иерархии);
-
предопределенные среды ссылок (для каждого ЯП определен стандартный контекст - это имена, которые знает компилятор). Например, в языке Паскаль к предопределенной среде ссылок относятся идентификаторы integer, real, boolean, true, false и т.д. В большинстве ЯП имена из предопределенных сред ссылок можно переопределять, но это не рекомендуется. В языке Ада считается, что любой модуль загружен внутрь модуля STANDARD (в этом модуле определены типы: символьный, булевский и др. - они образуют предопределенную среду ссылок).
-
нелокальные среды ссылок (промежуточный случай между локальной и глобальной средами ссылок). Откуда берутся нелокальные среды ссылок? Если процедуры организованы по типу call-return, то время жизни записи активации вызывающей программы полностью покрывает время жизни записи активации вызываемой программы. При этом контейнер, который содержит ЗА, ведет себя как стек.
Т.к. записи активации ведут себя вложенным образом, программы на современных ЯП имеют блочную структуру {...{..}...} (блочная структура возникает на уровне операторов).
Заметим, что все структуры, которые есть в ЯП, имеют вложенный характер. В теории языков структуры бывают:
-
проективные (структуры, которые перекрываются),
-
не проективные.
Естественные языки, в отличие от ЯП, имеют проективные структуры.
В языках Паскаль и Ада допускается вложение – например, 3-х звенная структура
процедуры:
заголовок
объявления
begin
операторы
end
По какому принципу устроены модули, процедуры?
Пример
procedure P is
X: integer;
...
begin
X:=N;
end P;
Интересно, что большинство современных ЯП более разумно относится к вложенности.
В языке Modula 2 одни библиотечные модули нельзя вкладывать в другие библиотечные модули. В языке Oberon (наследнике Modula 2) никакой вложенности не было: программа состоит из совокупности последовательных модулей. В языке Паскаль была полная вложенность процедур.
В современных же ЯП (начиная с языка Си) - вложенность процедур запрещена. Программа состоит из файлов, а каждый файл - это линейная последовательность определений ОД и функций. Все современные ЯП следуют по этому пути. Это сделано в силу того, что человек очень тяжело воспринимает вложенные программные структуры. Последовательные структуры воспринимаются легче.
Раньше в языке Паскаль допускалась вложенность блока в блок (вложенность процедур), и, как следствие, возникают нелокальные СС.
Пример
proc
j: integer;
proc
var i: integer;
i=j;
В современных ЯП вложенных структур быть не может. Но в языке Java остается понятие блока:
{Y x;
{...T x; … {...х...}…… }
}
При этом ссылки на нелокальные переменные не способствуют надежности программирования. В большинстве современных ЯП программист не может управлять нелокальными средами, образованными вложенными блоками. В приведенном примере объявление T x; скрывает объявление Y x;
В некоторых ЯП (например, в С++) есть возможность дойти до самых глобальных переменных. Указатель области действия ::имя означает, что мы берем имя, описанное в глобальной среде ссылок.
В 1973 г. появилась статья "О вредности глобальных переменных".
В языке Java описанная выше ситуация невозможна: если мы внутри блока объявляем переменную, которая одноименна какой-то нелокальной переменной или даже формальному параметру процедуры, то это является ошибкой.
Блочная структура также присутствует в ЯП за счет структуры модулей. В программировании есть понятие логического модуля. В современных ЯП (C#, Java) понятие логического модуля совпадает с понятием класса. В этих языках отсутствует понятие глобальной переменной, глобальной функции, т.е. программа на этих языках - это совокупность определений классов. Любая функция в этих ЯП - член какого-то класса. При этом класс, в котором описан соответствующий член, является локальной средой ссылок.
Считать ли, что локальная среда ссылок является частью имен класса?
Пример 1:
class
{
int y;
f(T x, y)
{
int y; //локальная среда ссылок функции f.
}
}
Пример 2:
int f (int x)
{
int x;//ошибка, т.к. формальные параметры являются частью локальной СС
...
}
В большинстве ЯП считается, что локальная среда ссылок функции - это набор переменных, описанных внутри функции (в том числе и формальные параметры). Класс образует ближайшую нелокальную среду ссылок.
В языке Delphi сделано следующее исключение: формальные параметры, с одной стороны, входят в локальную среду ссылок, а, с другой стороны, они одновременно являются частью нелокальной среды ссылок. Т.е. программист на языке Delphi не имеет права объявлять формальные параметры, имена которых совпадают с именами членов класса, где объявлена эта функция.
Если класс Y унаследован из класса X, то считается, что среда ссылок класса Y вложена внутрь среды ссылок класса X.
Тем самым, среды ссылок в современных ЯП имеют вложенную, блочную природу.
Ранее мы обсуждали, что бывают динамические и статические области видимости. Понятие область видимости очень тесно связано с понятием среды ссылок. Пока мы обсуждали статические среды ссылок. В некоторых ЯП (нетрадиционных - например, LISP) возникают динамические среды ссылок, и у программиста есть возможность управлять нелокальными средами. В традиционных и объектно-ориентированных ЯП используются статические среды ссылок из соображений эффективности. Если абстрагироваться от проблем идентификации объектов данных, можно посмотреть на проблему несколько шире. Даже в традиционных ЯП мы увидим динамические среды ссылок. Речь идет о понятии исключительной ситуации в С++. Есть оператор возникновения исключительной ситуации:
throw exp;
и "ловушка"
catch(T e) {...}
С некоторой точки зрения, ловушка исключительной ситуации напоминает процедуру.
У исключительной ситуации тоже есть своя локальная среда ссылок. Каким образом устанавливается соответствие между оператором, возбуждающим исключение, и соответствующей "ловушкой"? Среды ссылок для поиска обработчика ведут себя динамически.
В то же время, в языке Ада объявления исключений ведут себя как переменные. Возникает несоответствие: динамическая природа поиска "ловушки" и статическая природа объявлений исключений.
Пункт 4.
Передача параметров.
Есть:
-
семантика передачи параметров,
-
механизм передачи параметров.
Из рассматриваемых языков наиболее логично поступили в языке Ада.
Во всех языках, кроме Ада, фиксируется механизм передачи параметров, из которого мы выводим семантику. В языке Ада зафиксирована семантика передачи параметров, а механизмы передачи параметров для реализации этой семантики обсуждаются в стандарте.
Есть:
CALLER - вызывающая программа и
SUB - вызываемая программа.
При передаче информации между ними возникают:
-
входные параметры (параметры, от которых требуется их значение)
-
выходные параметры (одной из задач процедуры является присваивание значений выходным параметрам);
-
входные и выходные параметры.
Входные параметры могут быть:
-
константные (не могут изменяться) и
-
не константные.
Это классификация формальных параметров.
В языке Ада входные параметры называются in (не константных входных параметров нет); выходные - out; входные-выходные - inout.
Когда мы описываем параметры в процедуре, мы должны перед типом переменной описывать ее класс (по умолчанию считается, что класс входной (in)). Например:
procedure P (x: integer) is ... end P;
- то же самое, что
procedure P (x: in integer) is ... end P;
Обратим внимание, что процедура, у которой есть параметр out или inout, - это процедура с побочным эффектом.
Вредными считаются побочные эффекты, которые приводят к изменению нелокальной среды (изменение глобальных переменных или обращение к процедурам ввода – вывода).
В языке Ада предлагалось три вида процедур:
-
классические процедуры;
-
функции (это процедуры, которые возвращают значение, но не меняют своих параметров);
-
процедуры, возвращающие значение, но обладающие побочным эффектом (меняющие значения своих параметров) - в окончательном варианте языка от них отказались.
Отметим, что в языках Pascal, Algol60, Fortran было четкое разделение на процедуры и функции. В большинстве современных языков можно было обращаться к функциям как к процедурам, т.е. игнорировать возвращаемое значение.
В современных ЯП (Delphi, С++, C#, Java) разницы между процедурой и функции нет.
В большинстве ЯП (за исключением Ада) вместо семантики фиксируются механизмы (способы) передачи параметров. Рассмотрим способы передачи параметров:
-
по значению (этот способ есть во всех ЯП). Запись активации содержит некоторые данные: она инициализируется в прологе. В прологе фактические параметры копируются в формальные параметры. При способе передачи параметров по значению для каждого формального параметра в записи активации выбирается свое место в соответствие с описанием этого параметра. При таком способе реализации допустимы как константные, так и не константные входные параметры (потому, что изменения происходят не фактического параметра, а некоторой области памяти, которая выделена под формальный параметр).
-
по результату (впервые появился в ALGOL W). При этом в эпилоге (т.е. после завершения основного кода процедуры) значения формальных параметров копируются в фактические параметры. Очевидно, что такой способ передачи отвечает семантике выходного параметра.
-
по значению и результату (комбинация 1-го и 2-го способов), т.е. в прологе мы копируем значение фактического параметра в область записи активации, которая отвечает за формальный параметр; в эпилоге копируем обратно.
Эти способы основаны на копировании значений; основной их недостаток – при копировании больших параметров возникают накладные расходы. Например, при вычислении определителя матрицы передавать матрицу в функцию по значению очень накладно.
-
по ссылке. В языке Си этот способ должен моделироваться программистом явно (вводится операция взятия адреса, и мы передаем адрес соответствующего параметра). При этом способе передачи параметров в записи активации хранятся адреса параметров. Очевидно, что передача параметров по ссылке может моделировать любой из трех видов передачи параметров.
В языке Фортран не было понятия адресной операции, поэтому был только один способ передачи параметров. В некоторых реализациях Фортрана применялся способ передачи параметров по значению и результату.
Пример:
SUBRUTINE P(X,Y,|Z|) - это означает, что параметр Z передается по значению и результату.
Массивы всегда передавались по ссылке, но для простых переменных передача по значению и результату бывает более эффективна.
Передача параметров по ссылке гарантирует полный доступ. Поэтому в языке С++ (где есть способ передачи параметров по ссылке), в отличие от стандарта ANSI C, требуется явное прототипирование всех функций (т.к. может быть не понятно, передавать значение или ссылку на объект).
В С++ также есть понятие константной ссылки:
сonst T& X .
Это означает, что параметр передается по ссылке, но модифицировать этот параметр нельзя (т.е. фактически это передача по значению).
-
передача параметров по имени. Это наиболее естественный способ передачи параметров. Если в качестве фактического параметра выступает простая переменная, особой разницы в способе передачи параметров по имени или по ссылке нет. Если передается более сложная переменная (например, элемент массива a[i]) по имени, то меняется адрес этого параметра.
Рассмотрим классический пример: процедура swap(x,y) меняет местами значения своих параметров.
Алгоритм:
proc swap(x,y)
t:=x;
x:=y;
y:=t;
end swap
Пусть передача параметров по ссылке.
Пусть i:=1; a[1]=3; a[2]=2; a[3]=1;
Обращение - swap (a[i],i);
Результат: a[1]=1;a[2]=2;a[3]=1;i=3.
Рассмотрим вызов swap(i,a[i]); - будет то же самое.
Рассмотрим теперь способ передачи по имени (пусть язык типа Algol 60). Способ передачи по имени означает, что мы должны взять и вместо соответствующих переменных x и y подставить a[i] и i.