Этот способ организации данных подобен предыдущему, но содержит средства контроля для определения некорректных обращений. Для определения данных этого типа добавляется атрибут ENVIRONMENT (среда, окружение).
declare X ENVIRONMENT(STACK);
Для пользователя Х — это стек, к которому можно обращаться из процедуры STACK (например, с помощью функций POP, PUSH, EMPTY) аналогично тому, как если бы Х было действительное число и им можно было бы оперировать, используя операции сложения или умножения. Пользователь не знает, как составлен стек, и не имеет доступа к его внутренней структуре так же, как и большинство программистов находятся в неведении относительно того, каким образом величина с плавающей точкой записывается в память ЭВМ. Стек объявляется компилятором как переменная-указатель, но при этом накладываются дополнительные ограничения:
1) обработка стека Х происходит в процедуре FUNCTION в модуле с именем STACK;
2) описание стека Х может находиться только в операторе REP (представление для переменной) в модуле STACK;
3) любые другие обращения к стеку Х запрещены.
С точки зрения пользователя, данные абстрактного типа не должны отличаться от других переменных в программе. Однако имеется одно существенное различие между переменными типа FIXED и BLIPPO. Переменная типа FIXED размещается в памяти, когда начинает выполняться процедура (или блок), содержащая объявление переменной, и освобождает память, когда процедура (блок) завершится. А для переменной типа BLIPPO должна быть выделена постоянная память. Это может привести к двусмысленной ситуации. Так как одна из целей применения данных абстрактного типа заключается в расширении типов данных для прикладных задач, различие между типами данных, генерируемых компилятором, и данных, созданных пользователями, может вызвать недоразумения. Следовательно, одной из задач проектирования с использованием данных абстрактного типа должно быть автоматическое размещение таких данных при их использовании, так чтобы не было различий в стратегии использования памяти для указанных двух типов данных.
Для того чтобы использовать автоматическое распределение памяти, переменная Х инициируется при вызове модуля STACK. По существу, компилирование объявления ENVIRONMENT эквивалентно следующей записи.
declare X POINTER INITIAL(STACK);
При первоначальном обращении к модулю STACK распределяется память для стека Х и возвращается ее адрес в переменную-указатель. Модуль, в котором происходит обработка стека Х, имеет вид:
STACK: ABSTRACTION;
REP 1 STACK[X], /* имя параметра стека*/
2 ENTRIES(100) FIXED,
2 TOPOFSTACK FIXED;
/* разместить стек */
/* присвоить начальные значения */
TOPOFSTACK = 0;
PUSH: function(X, элемент);
...
end;
POP: function(X, элемент);
...
end;
...
end;
Такую процедуру легко написать на современных языках, учитывая следующее.
Начало процедуры ABSTRACTION предназначено для размещения данных абстрактного типа и присвоения им начальных значений; однако действительным распределением памяти автоматически занимается компилятор. Это делается для сохранения совместимости с элементарными типами данных, такими, как FIXED и REAL.
Слово ABSTRACTION заменяется ключевым словом PROCEDURE и добавляется атрибут RETURN(POINTER). Кроме того, используются следующие операторы:
declare DUMMY POINTER;
ALLOCATE datatype SET(DUMMY);
Эти операторы позволяют автоматически распределять память для данных абстрактного типа. Для того чтобы обеспечить возврат из подпрограммы распределения памяти к соответствующему оператору declare, перед первым оператором FUNCTION помещают оператор RETURN(DUMMY). Вместо оператора REP объявляется структура типа BASED. Таким образом, транслятор транслирует следующие операторы:
STACK: procedure RETURNS(POINTER);
declare 1 STACK BASED(X),
2 ENTRIES(100) FIXED,
2 TOPOFSTACK FIXED;
declare DUMMY POINTER;
ALLOCATE STACK SET(DUMMY);
TOPOFSTACK = 0;
return(DUMMY);
end;
Вместо оператора FUNCTION подставляется последовательность ENTRY BEGIN. Для соответствующих операторов END добавляются операторы RETURN. Таким образом, имеем:
STACK: procedure RETURNS(POINTER);
declare 1 STACK BASED(X),
2 ENTRIES(100) FIXED,
2 TOPOFSTACK FIXED;
declare DUMMY POINTER;
ALLOCATE STACK SET(DUMMY);
TOPOFSTACK = 0;
return(DUMMY)
PUSH: function(X, элемент);
...
return;
end;
POP: function(X, элемент);
...
return;
end;
...
end;
Доступ к данным. При указанном способе организации данных защитные меры приняты лишь отчасти: модуль не может получить представление любой переменной абстрактного типа, определенное в некотором другом модуле. Однако должен ли вообще данный модуль иметь доступ к определенным данным абстрактного типа? Окончательная версия программы имеет следующую структуру:
Модуль1
Модуль2
…
Модульn
При такой структуре программы каждый модуль может пользоваться любой функцией любого другого модуля. Однако это может оказаться нежелательным. Предположим, что модуль X использует абстрактный тип данных Y, который, в свою очередь, обращается к абстрактному типу данных Z (рис. 4.14).
Х вызывает Z непосредственно
б
Рис. 4.14 — Нарушение принципа информационной локализованности
При написании программы программист, стремясь к большей эффективности программы, записывает вызов из модуля Х данных Z, тем самым неумышленно нарушает спецификацию относительно того, что о структуре данных Z известно только в модуле Y. Однако компилятор не в состоянии проверить это нарушение, а ошибка может быть выявлена гораздо позже, например, если модуль Y когда-либо изменится.
В операционных системах имеется возможность защиты от некорректных действий; такую форму защиты можно добавить в языки программирования. В операционных системах имеется вектор возможностей, связанный с каждым независимо выполняемым процессом. Этот вектор описывает функции, которые может выполнять данный процесс. Так как процессы начинаются и заканчиваются, когда задания пользователя соответственно поступают в систему или выходят из нее, вектор возможностей динамичен, и производится проверка правомерности операций во время работы системы.
В языках программирования аналогом такого вектора является право доступа. По сравнению с вектором возможностей права доступа обладают одним значительным преимуществом: проверка производится во время компиляции, а не во время выполнения программы. Программа статична, все ее процедуры известны до начала выполнения, все абстрактные типы данных известны, и проверка правильности обращения к данным может быть выполнена компилятором.
Заметим, что цель использования прав доступа заключается в ограничении доступа к абстрактным типам данных, а не в обеспечении безопасности системы. Предполагается, что программист может обращаться ко всем элементам системы, поэтому права доступа могут изменяться.