Copyright (C) А.Гавва V-0.4w май 2004

5. Элаборация

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

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

5.1 Код элаборации

Стандарт Ada95 предусматривает общие правила выполнения кода в процессе элаборации (иначе, кода элаборации). Обработка кода элаборации осуществляется в трех контекстах:

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

В отношении кода элаборации существует один важный аспект: необходима уверенность в том, что выполнение кода элаборации осуществляется в соответствующем порядке. Программа обладает множеством секций кода элаборации (потенциально, каждый модуль программы содержит одну секцию кода элаборации). Следовательно, важна правильность последовательности, в которой осуществляется выполнение этих секций. Для примера описания переменной 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++), программист вынужден заботиться о порядке элаборации самостоятельно. В следствие этого, часто встречаются случаи написания программ в которых выбор неправильного порядка элаборации приводит к неожиданным результатам, поскольку при этом используются переменные, значения которых не инициализированы. Язык Ада был разработан как надежный язык и заботы программиста о порядке элаборации не так значительны. В результате, язык предусматривает три уровня защиты:

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

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

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

5.3 Управление порядком элаборации

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

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

Примечательно, что в отличие от директив 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. Однако, как мы уже видели, существуют случаи, когда эти три директивы компилятора не могут быть использованы. Таким образом, для клиентов предусмотрены дополнительные методы управления порядком элаборации серверов от которых эти клиенты зависят:

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

Суть правила проста. Если модуль содержит код элаборации, который может прямо или косвенно осуществить вызов подпрограммы модуля указанного в спецификаторе with, или конкретизировать настраиваемый модуль расположенный внутри модуля указанного в спецификаторе with, то в случае, когда модуль, который указан в спецификаторе with, не содержит директиву Pure, Preelaborate или Elaborate_Body, клиент должен указывать директиву Elaborate_All для модуля, который указан в спецификаторе with. Соблюдение этого правила гарантирует клиенту, что вызовы и конкретизация настраиваемых модулей могут быть выполнены без риска возбуждения исключения. Если это правило не соблюдается, то программа может попасть в одно из четырех состояний:

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

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

Если в точке, в которой создается ссылочное значение, известно тело подпрограммы, для которого необходимо выполнить элаборацию, то ссылочное значение будет безопасным и его использование не требует проверки. Это может быть достигнуто соответствующей организацией последовательности описаний, если подпрограмма расположена в текущем модуле, или использованием директивы Pure, Preelaborate или Elaborate_Body, когда подпрограмма расположена в другом модуле, который указан в спецификаторе with.

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

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


Copyright (C) А.Гавва V-0.4w май 2004