Copyright (C) А.Гавва V-0.4w май 2004
5. Элаборация
Процесс элаборации является процессом предварительной подготовки, который осуществляется непосредственно перед началом выполнения головной программы, тела инструкции блока или подпрограммы. Процесс элаборации также называют процессом выполнения описаний.
Следует заметить, что в других языках программирования подобные подготовительные действия, осуществляемые непосредственно перед запуском программы на выполнение, явно не определяются, а последовательность их выполнения неявно продиктована последовательностью компиляции и сборки приложения.
5.1 Код элаборации
Стандарт Ada95 предусматривает общие правила выполнения кода в процессе элаборации (иначе, кода элаборации). Обработка кода элаборации осуществляется в трех контекстах:
- Инициализация переменных - Переменные, описанные на уровне библиотеки в спецификациях или телах пакетов, могут требовать инициализацию, которая выполняется в процессе элаборации:
Sqrt_Half : Float := Sqrt (0.5);- Инициализация пакетов - Код в секции begin - end, на внешнем уровне тела пакета, является кодом инициализации пакета, и выполняется как часть кода элаборации тела пакета.
- Аллокаторы задач уровня библиотеки - Задачи, которые описаны с помощью аллокаторов на уровне библиотеки, начинают выполняться немедленно и, следовательно, могут выполняться в процессе элаборации.
Любой из этих контекстов допускает вызов подпрограмм. Это подразумевает, что любая часть программы может быть выполнена как часть кода элаборации. Более того, существует возможность написания программы, вся работа которой осуществляется в процессе элаборации, при этом, головная прорамма - пуста. Следует однако заметить, что подобный способ структурирования программы считается стилистически неверным.
В отношении кода элаборации существует один важный аспект: необходима уверенность в том, что выполнение кода элаборации осуществляется в соответствующем порядке. Программа обладает множеством секций кода элаборации (потенциально, каждый модуль программы содержит одну секцию кода элаборации). Следовательно, важна правильность последовательности, в которой осуществляется выполнение этих секций. Для примера описания переменной Sqrt_Half, который показан выше, это подразумевает, что если другая секция кода элаборации использует Sqrt_Half, то она должна выполняться после секции кода элаборации, который содержит описание переменной Sqrt_Half.
Порядок элаборации не вызывает проблем, когда соблюдается следующее правило: элаборация спецификации и тела, каждого указанного в спецификаторе with модуля, осуществляется перед элаборацией модуля, который содержит такой спецификатор with. Например:
with Unit_1; package Unit_2 is ...В данном случае необходимо, чтобы элаборация спецификации и тела модуля Unit_1 осуществлялась перед спецификацией модуля Unit_2. Однако, такое правило накладывает достаточно жесткие ограничения. В частности, это делает невозможным наличие взаимно рекурсивных подпрограмм, которые распологаются в разных пакетах.
Можно предположить, что достаточно "умный" компилятор способен проанализировать код элаборации и определить правильный порядок элаборации, но в общем случае это не возможно. Рассмотрим следующий пример.
Тело модуля Unit_1 содержит подпрограмму Func_1, которая использует переменную Sqrt_1 описанную в коде элаборации тела Unit_1:
Sqrt_1 : Float := Sqrt (0.1);Код элаборации тела модуля Unit_1 также содержит:
if expression_1 = 1 then Q := Unit_2.Func_2; end if;Существует также модуль Unit_2, структура которого аналогична: он содержит подпрограмму Func_2, которая использует переменную Sqrt_2 описанную в коде элаборации тела Unit_2:
Sqrt_2 : Float := Sqrt (0.1);Код элаборации тела модуля Unit_2 также содержит:
if expression_2 = 2 then Q := Unit_1.Func_1; end if;Для показанных фрагментов кода суть вопроса заключена в выборе правильного порядка элаборации, то есть выбрать последовательность вида:
Spec of Unit_1 Spec of Unit_2 Body of Unit_1 Body of Unit_2или последовательность вида:
Spec of Unit_2 Spec of Unit_1 Body of Unit_2 Body of Unit_1При внимательном рассмотрении можно обнаружить, что ответить на этот вопрос во время компиляции не возможно. Если выражение expression_1 не равно 1, а выражение expression_2 не равно 2, то можно использовать любую из показанных выше последовательностей элаборации, поскольку отсутствуют вызовы функций. В случае, когда результаты обеих проверок истинны (true), не может быть использована ни одна из показанных последовательностей элаборации.
Если результат проверки одного условия истина (true), а другого - ложь (false), то одна из показанных последовательностей элаборации будет справедлива, а другая - нет. Например, если expression_1 = 1 и expression_2 /= 2, то присутствует вызов Func_2, но нет вызова Func_1. Это подразумевает, справедливость выполнения элаборации тела модуля Unit_1 перед элаборацией тела Unit_2, то есть первая последовательность элаборации правильна, а вторая - нет.
Если выражения expression_1 и expression_2 зависят от вводимых данных, то компилятор и/или редактор связей будут не в состоянии определить какое из условий будет истинным. Таким образом, для данного случая не возможно гарантировать правильность порядка элаборации во время выполнения программы.
5.2 Проверка порядка элаборации
В некоторых языках программирования, которые подразумевают наличие аналогичных проблем элаборации (например Java и C++), программист вынужден заботиться о порядке элаборации самостоятельно. В следствие этого, часто встречаются случаи написания программ в которых выбор неправильного порядка элаборации приводит к неожиданным результатам, поскольку при этом используются переменные, значения которых не инициализированы. Язык Ада был разработан как надежный язык и заботы программиста о порядке элаборации не так значительны. В результате, язык предусматривает три уровня защиты:
- Стандартные правила элаборации накладывают некоторые ограничения на выбор порядка элаборации. В частности, если какой-либо модуль указан в спецификаторе with, то элаборация его спецификации всегда осуществляется перед элаборацией модуля, который содержит этот спецификатор with. Подобным образом, элаборация спецификации модуля-предка всегда осуществляется перед элаборацией спецификации модуля-потомка, и, в заключение, элаборация спецификации всегда осуществляется перед элаборацией соответствующего тела.
- Динамические проверки элаборации осуществляются во время выполнения программы. Таким образом, если доступ к какой-либо сущности осуществляется до ее элаборации (обычно это происходит при вызовах подпрограмм), то возбуждается исключение Program_Error.
- Средства управления элаборацией позволяют программисту явно указывать необходимый порядок элаборации.
Рассмотрим перечисленные возможности более детально. Во-первых, правила динамической проверки. Одно из правил заключается в том, что при попытке использования переменной, элаборация которой не была выполнена, возбуждается исключение Program_Error. Недостатком такого подхода является то, что затраты производительности на проверку каждой переменной могут оказаться достаточно дорогостоящими. Вместо этого, Ada95 поддерживает два несколько более строгих правила, соблюдение которых легко проверить:
- Ограничения для вызовов подпрограмм - Какая-либо подпрограмма может быть вызвана в процессе элаборации только в случае, когда осуществлена элаборация тела этой подпрограммы. Такое правило гарантирует, что элаборация спецификации подпрограммы будет выполнена перед вызовом подпрограммы, а не перед элаборацией тела. При нарушении этого правила возбуждается исключение Program_Error.
- Ограничения для конкретизации настраиваемых модулей - Настраиваемый модуль может быть конкретизирован только после после осуществления элаборации тела настраиваемого модуля. Такое правило гарантирует, что элаборация спецификации настраиваемого модуля будет выполнена перед конкретизацией настраиваемого модуля, а не перед элаборацией тела. При нарушении этого правила возбуждается исключение Program_Error.
Смысл идеи заключается в том, что при осуществлении элаборации тела элаборация любых переменных, которые используются в этом теле, должна быть уже выполнена. Таким образом, при проверке элаборации тела гарантируется, что ни один из ресурсов, которые использует это тело, не окажется источником каких-либо неожиданностей. Как было замечено ранее, такие правила несколько более строгие, поскольку необходима гарантия корректности вызова подпрограммы, которая использует в своем теле не локальные для этой подпрограммы ресурсы. Однако, полностью полагаться на эти правила будет не безопасно, поскольку это подразумевает, что вызывающая подпрограмма должна заботится о деталях реализации, которые размещенны в теле, что противоречит основным принципам Ады.
Вероятная реализация такой идеи может выглядеть следующим образом. С каждой подпрограммой и с каждым настраиваемым модулем может быть ассоциирована логическая переменная. Первоначально такая переменная имеет значение "ложь" (False), а после осуществления элаборации тела эта переменная устанавливается в значение "истина" (True). Проверка значения этой переменной выполняется при осуществлении вызова или конкретизации, и когда значение переменной - "ложь" (False), возбуждается исключение Program_Error.
5.3 Управление порядком элаборации
Выше мы обсудили правила согласно которых возбуждается исключение Program_Error, когда выбран неправильный порядок элаборации. Это предотвращает ошибочное выполнение программы, но Ада также предоставляет механизмы, которые позволяют явно указавать необходимый порядок элаборации и избежать возбуждение исключения. Следует заметить, что при разработке небольших программных проектов, как правило, нет необходимости вмешиваться в последовательность элаборации множества программных модулей. Однако, при разработке больших систем, такое вмешательство бывает необходимым.
Во-первых, существует несколько способов указать компилятору, что данный модуль потенциально не содержит никаких проблем связанных с элаборацией:
- Пакеты, которые не требуют наличия тела.
Ада не допускает наличие тела для библиотечного пакета, который не требует наличия тела. Это подразумевает, что можно иметь пакет подобный следующему:
package Definitions is generic type m is new integer; package Subp is type a is array (1 .. 10) of m; type b is array (1 .. 20) of m; end Subp; end Definitions;В данном случае, пакет, который указывает в спецификаторе with пакет Definitions, может безопасно конкретизировать пакет Definitions.Subp поскольку компилятор способен определить очевидное отсутствие тела
- Директива компилятора pragma Pure
Данная директива накладывает строгие ограничения на модуль, гарантируя, что обращение к любой подпрограмме модуля не повлечет за собой никаких проблем элаборации. Это подразумевает, что компилятору нет нужды заботиться о порядке элаборации подобного модуля, в частности, нет необходимости осуществлять проверку обращений к подпрограммам в этом модуле.
- Директива компилятора pragma Preelaborate
Данная директива накладывает несколько менее строгие ограничения на модуль чем директива Pure. Однако эти ограничения остаются достаточно значительными, чтобы гарантировать отсутствие проблем при вызове подпрограмм модуля.
- Директива компилятора pragma Elaborate_Body
Данная директива требует, чтобы элаборация тела модуля была осуществлена сразу после элаборации спецификации. Предположим, что модуль A содержит данную директиву, а модуль B указывает в спецификаторе with модуль A. Напомним, что стандартные правила требуют, чтобы элаборация спецификации модуля A была выполнена перед элаборацией модуля, который указывает модуль A в спецификаторе with. Указание этой директивы компилятора в модуле A говорит о том, что элаборация тела модуля A будет выполнена перед элаборацией B. Таким образом, обращения к модулю A будут безопасны и не нуждаются в дополнительных проверках.
Примечательно, что в отличие от директив Pure и Preelaborate использование директивы Elaborate_Body не гарантирует, что программа свободна от проблем связанных с элаборацией, поскольку возможно наличие ситуации, которая не удовлетворяет требования порядка элаборации. Вернемся к примеру с модулями Unit_1 и Unit_2. Если поместить директиву Elaborate_Body в Unit_1, а модуль Unit_2 оставить без изменений, то порядок элаборации будет следующий:
Spec of Unit_2 Spec of Unit_1 Body of Unit_1 Body of Unit_2В этом случае подразумевается, что нет необходимости проверять вызов Func_1 из модуля Unit_2 поскольку он должен быть безопасным. Вызов Func_2 из модуля Unit_1, когда результат вычисления выражения Expression_1 равен 1, может быть ошибочным. Следовательно, ответственность за отсутствие подобной ситуации возлагается на программиста.
Когда все модули содержат директиву компилятора Elaborate_Body, то проблемы порядка элаборации отсутствуют, кроме случаев когда вызовы подпрограмм осуществляются из тела, забота о котором в любом случае возлагается на программиста. Следует заметить, что повсеместное использование этой директивы не всегда возможно. В частности, для показанного ранее примера с модулями Unit_1 и Unit_2, когда оба модуля содержат директиву Elaborate_Body, отсутствие возможности определить правильный порядок элаборации очевидно.
Показанные выше директивы компилятора позволяют гарантировать клиентам безопасное использование серверов, и такой подход является предпочтительным. Следовательно, маркирование модулей как Pure или Preelaborate, если это возможно, является хорошим правилом. В противном случае следует маркировать модули как Elaborate_Body. Однако, как мы уже видели, существуют случаи, когда эти три директивы компилятора не могут быть использованы. Таким образом, для клиентов предусмотрены дополнительные методы управления порядком элаборации серверов от которых эти клиенты зависят:
- Директива компилятора pragma Elaborate (unit)
Эта директива помещается после указания спецификатора with, и она требует, чтобы элаборация тела указанного модуля unit осуществлялась перед элаборацией модуля в котором указывается эта директива. Эта директива используется когда в процессе элаборации текущий модуль вызывает, прямо или косвенно, какую-либо подпрограмму модуля unit.
- Директива компилятора pragma Elaborate_All (unit)
Это более строгая версия директивы Elaborate. Рассмотрим следующий пример:
Модуль A указывает в with модуль B и вызывает B.Func в процессе элаборации Модуль B указывает в with модуль C, и B.Func вызывает C.FuncЕсли поместить директиву Elaborate (B) в модуль A, то это гарантирует, что элаборация тела B будет выполнена перед вызовом, однако элаборация тела C перед вызовом не выполняется. Таким образом, вызов C.Func может стать причиной возбуждения исключения Program_Error. Результат действия директивы Elaborate_All более строгий. Она требует, чтобы была осуществлена предварительная элаборация не только тела того модуля, который указан в директиве (и в спецификаторе with), но и тела всех модулей, которые используются указанным модулем (для поиска используемых модулей используется транзитивная цепочка спецификаторов with). Например, если поместить директиву Elaborate_All (B) в модуль A, то она потребует, чтобы перед элаборацией модуля A была осуществлена элаборация не только тела модуля B, но и тела модуля C, поскольку модуль B указывает модуль C в спецификаторе with.
Теперь можно продемонстрировать использование этих правил для предупреждения проблем связанных с элаборацией, в случаях, когда не используются динамическая диспетчеризация и ссылочные значения для подпрограмм. Такие случаи будут рассмотренны несколько позже.
Суть правила проста. Если модуль содержит код элаборации, который может прямо или косвенно осуществить вызов подпрограммы модуля указанного в спецификаторе with, или конкретизировать настраиваемый модуль расположенный внутри модуля указанного в спецификаторе with, то в случае, когда модуль, который указан в спецификаторе with, не содержит директиву Pure, Preelaborate или Elaborate_Body, клиент должен указывать директиву Elaborate_All для модуля, который указан в спецификаторе with. Соблюдение этого правила гарантирует клиенту, что вызовы и конкретизация настраиваемых модулей могут быть выполнены без риска возбуждения исключения. Если это правило не соблюдается, то программа может попасть в одно из четырех состояний:
- Правильный порядок элаборации отсутствует:
Отсутствует порядок элаборации, который соблюдает правила учитывающие использование любой из директив Pure, Preelaborate или Elaborate_Body. В этом случае, компилятор Ada95 обязан распознать эту ситуацию на этапе связывания, и отвергнуть построение исполняемой программы.
- Порядок элаборации, один или более, существует, но все варианты не правильны:
В этом случае, редактор связей может построить исполняемую программу, но при выполнении программы будет возбуждаться исключение Program_Error.
- Существует несколько вариантов порядка элаборации, некоторые правильны, некоторые - нет:
Подразумевается, что программист не управляет порядком элаборации. Таким образом, редактор связей может выбрать или не выбрать один из правильных вариантов порядка элаборации, и программа может не возбуждать или возбуждать исключение Program_Error во время выполнения. Этот случай является наихудшим, поскольку программа может перестать выполняться после переноса в среду другого компилятора, или даже в среду другой версии того же самого компилятора.
- Порядок элаборации, один или более, существует, все варианты правильны:
В этом случае программа выполняется успешно. Такое состояние может быть гарантировано при соблюдении указанных ранее правил, но оно также может быть получено без соблюдения этих правил.
Следует обратить внимание на одно дополнительное преимущество соблюдения правила Elaborate_All, которое заключается в том, что программа продолжает оставаться в идеальном состоянии даже в случае изменения тел некоторых подпрограмм. В противном случае, если программа, которая не соблюдает указанное правило, в некоторый момент времени оказывается безопасной, то это состояние программы может также незаметно исчезнуть, в результате изменений вызванных нуждами сопровождения программы.
Введение ссылочных типов для подпрограмм усложняется обработкой элаборации. Трудность заключается в том, что не возможно определить вызываемую подпрограмму на этапе компиляции. Это подразумевает, что в таком случае редактор связей не может проанализаровать требования элаборации.
Если в точке, в которой создается ссылочное значение, известно тело подпрограммы, для которого необходимо выполнить элаборацию, то ссылочное значение будет безопасным и его использование не требует проверки. Это может быть достигнуто соответствующей организацией последовательности описаний, если подпрограмма расположена в текущем модуле, или использованием директивы Pure, Preelaborate или Elaborate_Body, когда подпрограмма расположена в другом модуле, который указан в спецификаторе with.
Если тело используемой подпрограммы, для которого необходимо выполнить элаборацию, не известно в точке создания ссылочного значения, то любое используемое ссылочное значение должно подвергаться динамической проверке, и такая динамическая проверка должна возбуждать исключение Program_Error в случае, когда элаборация тела не осуществлена.
При использовании динамической диспетчеризации для тэговых типов необходимые динамические проверки генерируются аналогичным образом. Это означает, что в случае вызова любой примитивной операции тэгового типа, который выполняется до элаборации тела примитивной операции, будет возбуждаться исключение Program_Error.
Copyright (C) А.Гавва V-0.4w май 2004