Популярные услуги

Все письменные КМ под ключ за 3 суток! (КМ-6 + КМ-7 + КМ-8 + КМ-9 + КМ-10)
КМ-6. Динамические массивы. Семинар - выполню любой вариант!
Любая задача на C/C++
Одно любое задание в mYsql
Любой тест по базам данных максимально быстро на хорошую оценку - или верну деньги!
Любой реферат по объектно-ориентированному программированию (ООП)
Повышение уникальности твоей работе
КМ-2. Разработка простейших консольных программ с использованием ООП + КМ-4. Более сложные элементы ООП - под ключ!
Любой реферат по информатике
КМ-7. Решение задач на обработку символьной информации - выполню любой вариант!

Лекция 15

2021-03-09СтудИзба

Лекция № 15

Одностороннее связывание, которое есть во всех ЯП. У нас есть модуль сервера, интерфейс, которого предлагает некоторый набор типов данных и имен переменных, и есть модуль клиента. Особенность его в том, что серверный модуль никак не зависит от клиентского, т.е. серверный модуль ничего не знает о клиентском. Интерфейсная часть серверного модуля импортируется клиентскому.


Сервер                                                           Клиент

Некоторая конструкция

           импорта

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

Рекомендуемые материалы

В языках Модула-2, Оберон есть предложения Import, в языке Delphi есть предложение uses, и, наконец, язык Ада, который несколько расширяет понятие модуля тем, что в одном физическом модуле может образовываться совокупностью нескольких логических. Есть понятие «единицы компиляции». И если в более простых языках (Delphi, Оберон, Модула-2) единица компиляции тоже самое, что и библиотечный модуль, т. е. логическое понятие, то в Аде – это некоторая последовательность логически связанных модулей, но, все равно, предложения, типа выше указанных, остаются.

Ада   WITH  (список имен)

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

STANDART

ЕК1,ЕК2


                                   …………..


В случае, когда речь идет о раздельной компиляции. Если вместо этих ЕК взять и поставить текст, то мы компилируем все за один раз в составе одного модуля STANDART. Имена из модуля STANDART импортировать не надо. Они предполагаются уже импортированными. В случае же физически раздельной компиляции считается, что виден только модуль STANDART. Чтобы стали видны остальные ЕК, нужен конструктор имен, который дает только потенциальную видимость, если мы хотим непосредственную видимость (все имена, конечно не конфликтующие, становятся видимы), конструкцию

Uses (список имен)

можно использовать в раздельной трансляции.

Мы уже говорили о том, что Ада позволяет нам двустороннее связывание. Зачем оно нужно? Ни в одном из ранее перечисленных языков это понятие не требовалось. Это по тому, что у нас отсутствовали вложенные модули. Ни в Delphi, ни в Модуле-2, ни в Обероне у нас один модуль в другой вкладываться не мог. В Аде другая ситуация. Например, у нас есть модуль MAIN, внутри которого сидит модуль М1, М2 и еще какая-нибудь процедура Р.

MAIN

PROC P
, М1, М2


С помощью одностороннего связывания это можно сказать, что у нас есть

MAIN                           М1                          М2                      PROC P


В принципе, можно обойтись и обычным односторонним связыванием, но эта ситуация чем может быть нехороша? Например, модули М1 и М2 могут иметь смысл только в контексте модуля MAIN, равно как и процедура Р. Речь здесь идет только об интерфейсных частях. Естественно у модуля есть еще и тело. Т. е. у пакета есть идентификатор пакета и есть тело пакета (его реализация). Аналогичная ситуация и с процедурой (интерфейс процедуры есть ее прототип). При такой реализации модули М1 и М2 не имеют никакого отношения к реализации пакета MAIN. Т.е. они должны осуществлять действия, которые не зависят от реализации MAIN. А в случае вложенной структуры, у нас вложенные объекты должны иметь доступ ко всем ресурсам модуля MAIN, а не только к тому, что описано и не только в интерфейсной части.    Т.е. это настоящий вложенный объект. Подобного рода вещи с помощью одностороннего связывания реализовать очень и очень тяжело. Такое проектирование называется «проектирование сверху вниз», и Ада один из языков, которые мы сейчас рассматриваем, позволяет нам реализовать такую гибкую схему.  Синтаксически это можно реализовать так.

Есть package body is

(мы хотим, чтобы М1 и М2 представляли из себя внутренность модуля MAIN), и в тоже время мы хотим реализовать раздельную компиляцию. Для этого есть будем использовать двустороннее связывание. Есть так называемая заглушка и указание на контекст.

Package body MAIN is package body M1 is separate

                                      package body  M2 is separate

procedure P (X:INTEGER) is separate

end MAIN;

  То, что описано внутри MAIN, никак не доступно для других объектов. Достать их нет никакой возможности. При компиляции нам помимо интерфейса нужно еще указание контекста (т.к. нужны все ресурсы модуля MAIN        ). Мы оформляем это в виде отдельной единицы компиляции и пишем

Separate (MAIN)

   package body M1 is procedure PP is separate;

end MAIN;

separate (MAIN           )

  procedure P (X: INTEGER) is

    ………………

end P;

Аналогично  все то же самое  надо описать для М2.

 Интересно, что вложенность может быть абсолютно любая. Т. е. внутри, например, модуля М1 мы можем описать еще одну своженную структуру.      

separate (MAIN           М1)

  procedure PP is

    ………………

end PP;

Это получится вторичный модуль. Его отличие от первичного в том, что имена их указываются с именем первичного. Так вот внутри вторичного модуля в свою очередь могут быть другие вторичные модули. Чтобы нормально откомпилировать РР нужна полная информация о М1 и MAIN. Т.е.  двустороннее связывание реализуется через заглушку и указание контекста. В Аде можно спроектировать цельную программу, а потом отдельные логические куски выдернуть и откомпилировать отдельно. Надо сказать, что другие ЯП с этой точки зрения до Ады еще не дотянули.

Рассмотрим механизм зависимой трансляции в языках, которые основаны на классах. Все ЯП, которые мы до этого рассматривали, включая и Аду, с точки зрения раздельной, зависимой трансляции, характеризуются тем, что  вопрос о физическом нахождение файла (понятно, что модуль в ЯП – это есть некоторый текстовый файл, возникает вопрос, где они находятся) просто игнорировали. Считалось, что это задача системного программирования, и ЯП этого не касался. В этом есть свой смысл, потому что не хотелось зависеть от реализации какой-то конкретной файловой системы. Но сейчас любая общеупотребительная архитектура имеет иерархическую файловую систему. Имеется универсальный способ указания ресурса (текстовый файл, естественно, является ресурсом). Сегодня различия между файловыми системами минимальные. Если в Аде различия между физическим модулем и файлом игнорируются. Мы можем каждый логический модуль в файл поместить, а можем несколько логических модулей объединить в один физический файл, главное, что есть у нас понятие единицы компиляции. В Обероне , Delphi и Модула-2 один модуль – один файл. Создатели таких языков как Java и C# ориентировались на современные особенности файловых систем. И они решили немного обобщить эту схему. Программа на Java представляет из себя  некоторую совокупность приложений,  если проект мало-мальски сложный, то у нас возникают сгруппированные ресурсы.  

1. Стандартные библиотеки, то, что поставляется обычно с ЯП, но не является частью самих языков.

2. Сторонние компоненты, это то, что мы дополнительно покупаем, воруем или как-то еще достаем, т.е. приобретаем откуда-то извне.

3. Свои компоненты

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

1 класс = 1 файл

Этого обычно требуют все реализации Java, правда есть исключения, которые требуют немного поменьше, а именно, главный класс, где есть статическая функция Main, должен быть в отдельном файле, имя этого файла должно совпадать с именем класса, и соответственно имя получающегося приложения будет иметь имя этого файла. А вспомогательные классы уже могут располагаться по-другому.  Вот Вам и раздельная трансляция. Все классы группируются в некоторый пакет. Пакет – это совокупность классов. А как следствие – совокупность файлов. Так вот, если в предыдущих языках ничего не говорилось о том, где эти файлы должны располагаться, то пакет должен располагаться в том месте (на одном уровне файловой системы), где находится некоторая совокупность файлов. Предполагается, что все классы, которые входят в данное приложение хранятся в одной директории и образуют пакет. Наша задача, указать кокой пакет к чему относится. По этому первым объявлением в файле должно быть Package ‘имя объекта’. Пакет образован всеми файлами, которые приписывают себя к соответствующему пакету.

Например:

Rectangle.java

package Graphics;                              имя некоторой директории на некотором уровне файловой системы

   public class Rectangle {

………….

};

 Сами пакеты могут быть сложены в другие пакеты (не надо путать с Адой). Здесь аналогично тому, что папки в файловой системе могут быть вложены в другие папки, т.е. сами папки могут иметь иерархическую структуру.

 Фирма SUN предложила следующий момент. У нас есть понятие универсальная нотация того, где находится наш проект.


com.company.MyEditPji.Graphics

com.company.MyEditPji.InputOutput                                   пакеты

 

Пакет имеет иерархическую структуру, но эта структура представляет связь приблизительно такую же, как любая команда разработчиков (есть главный большой шеф, есть лидер команды, под ним сидят несколько программистов). Это все не есть вложение ресурсов один в другой – здесь все отдельно. Логическая структура отражает не характер связи внутри проекта, а характер связи внутри бюрократии. Реальной вложенности здесь, конечно же, нет. Все эти пакеты ведут себя как односторонне связанные. Все ЯП, кроме Ада поддерживают исключительно односторонне связывание.

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

В Windows  

 D:JavaCLASSES;

E:MyProjects;

Это и есть две директории, с которых начинается поиск. Чтобы организовать импорт с односторонним связыванием, у нас есть конструкция

Import имя_пакета;

Import Graphics.*;

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

Import Graphics.Rectangle;

Это, если нас интересует только прямоугольник. Такая форма нужна для ускорения процесса компиляции, и получающийся файл будет иметь меньший размер, нежели в первом случае, потому что там компилятор будет импортировать все имена, которые есть в пакете, а  во втором случае достаточно  одного класса. Компилятор легко находит в соответствующей директории файл с именем Rectangle и из него извлекает необходимую информацию о классе. Более того, мы имеет право  не писать import.  Любое имя в языке Java имеет вид:


Имя               х;

Определяем имя объекта х. Откуда должно браться «С»? Оно берется либо из имени текущего пакета (берется по умолчанию), либо  оно должно быть описано в виде

Import M.*;

Если не хотим употреблять Import, то можем явно писать M.x;

Сами имена пакетов имеют точно такую же строчечную структуру. Следовательно, можно просто писать:

E:Company

И пакет может выглядеть так

MyEditPji.Graphics.Rectangle ;

Компилятор видит, что это имя пакета, он пытается отыскать в нужной директории имя поддиректории Graphics, там он ищет файл Rectangle. Из этого файла он вытаскивает необходимую информацию.  Интересно, что в случае языка Java информация об импортируемых из данного пакета типах хранится вместе с самим классом. Это делается из соображений безопасности. Т.е. у нас Rectangle.java компилятор транслирует его в байт-код, который будет записан в файл с именем Rectangle.class. Представление байт-кода – это не только сам байт-код, инструкции виртуальной Java-машине, но и вся интерфейсная информация. Вся информация предается вместе с программой. Это сделано из соображения безопасности. Когда какой-то класс загружается на машину, тут же идет контроль интерфейса. Т.е. мы не можем при ретрансляции обмануть виртуальную Java-машину поскольку информация о типах у нас находится вместе с программой. Получается, что явной конструкции импорта нет, но он осуществляется как бы неявно. Типичное одностороннее связывание. Серверный модуль, например Graphics, ничего не знает о модуле, который будет его импортировать. Несмотря на то, что у нас есть возможность вкладывания одного пакета в другой, речь идет о псевдо иерархической организации файла и нечего более. Здесь только одностороннее связывание. Механизм раздельной трансляции достаточно очевиден, на современных компьютерах поиск по каталогам осуществляется достаточно быстро. Расходами на трансляцию и ретрансляцию можно пренебречь. Поэтому такая схема с удовольствием принята большинством программистов.

Создатели языка С# сделали примерно то же самое, только назвали немного по-другому. Например, вместо понятия пакета у них присутствует понятие пространства имен. Точно так же, как в Java, каждый класс должен принадлежать некоторому пакету, при этом свою принадлежность он объявляет в первой команде файла.

Package имя_пакета

Имя пакета представляет из себя либо один идентификатор, либо совокупность идентификаторов, перечисленных через точку.

В С# каждое объявление имеет вид

Namespace  имя {

              .

              .

  (описание класса)    

              .

              .

};

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

Namespace N1 {

              Public class A {…};

                  Namespace N2 {

                          Public class B {…};

   };

};

В Java тоже была возможность вкладывать один пакет в другой. Если мы хотим использовать то, что описали о водном файле, в другом. Это можно сделать так:

Namespace X {

              N1. A x = new A ( );

              N1.N2 B y = new B ( );

Так мы видим, что вложенность присутствует как и в Java, но только там она на уровне файловой системы, а здесь мы про это забываем. Здесь намного удобнее реализовано с точки зрения видимости. Если на Java пришлось бы писать что-то типа

N1. A c = new A ( ); и пакет N1 пришлось бы импортировать.

Здесь достаточно будет написать:

      A d = new A ( );

Прямо в описании языка сказано, что конструкция такого рода

Namespace N1 {

    Namespace N2 {

        Namespace N3 {

        };

    };

};

эквивалентна записи

namespace N1, N2, N3

но это то же самое, если бы мы в Java написали бы

package N1.N2.N3 {

……………….

};

связь между понятием пакета в Java и понятием пространства имен в С# довона очевидная. Точно так же как в Java есть импорт, так же он есть в С# как явный так и неявный, только здесь свой синтаксис (язык С# похож на С, ни в коем случае не на Java).

            Using System.Console;   серверный модуль

           

Можем так жк и переименновывать

Using Cons = System.Console;

Теперь везде, где компилятор увидит Cons, он будет понимать, что это на самом деле  System.Console. Это сделано для возможности укорачивать запись. Для раздельной трансляции точно такие же, как в Java требования, чтобы все файлы находились в определенном месте файловой системы. Опять же подчеркнем, что связь исключительно односторонняя, т.е. серверный модуль едонообразен для всех клиентских.  Реальной вложенности нет, она есть только в языке Ада.

 Еще о раздельной трансляции можно сказать следующее. Изначально в 70-е – 80-е годы говорить об языке, пригодном для индустриального программирования, без раздельной трансляции не приходилось. Это требовалось, в первую очередь, для минимизации расходов на перетрансляцию. Например, в Модуле-2, если мы меняем реализацию, надо перекомпилировать только файл реализации, а все остальные клиентские модули не затрагиваются, поскольку не изменен интерфейс. Такая же схема реализована в языке Оберон и Delphi. В Аде немного другая ситуация.

Import имя_пакета

  Package P is

     Type T is private;

Private ….

    Type T is ……;

End P;

Если мы меняем что-то в пакете Р, мы вынуждены перетранслировать не только пакет Р, но и все, что ссылается на пакет Р т. е. все клиентские модули для пакета Р. Потому что компилятор производит перераспределение памяти, одно это уже достаточное основан6ие для перетрансляции. Следовательно накладные расходы на перетрансляцию в языке Ада немного больше. С современной точки зрения расходы на перетрансляцию становятся все менее и менее значимые. В современных ЯП любое изменение пакета приводит к перетрансляции всех модулей, которые используют данный пакет. В С++, о котором мы не говорили, поскольку там независимая трансляция, Страуструп сказал, что, если Вы хотите минимизировать расходы на перетрансляцию, то нужно это делать через абстрактные типы данных.

Зависимая трансляция хороша тем, что она позволяет нам контролировать модульную целостность во время трансляции, а в языке Java и при выполнение  (вся ниформация о типе данных сосредоточена в оттранслированном классе). Обмануть Java-машину практически невозможно.

Независимая трансляция.  Там есть единица компиляции и есть транслятор. Контекст должен быть указан в самой единице компиляции. В языке С единственно возможный был способ

Extern ….. // функции, переменные

 Типы данных надо было дублировать в каждой единице компиляции. Хорошим стилем программирования считается, что у нас есть модуль , и его интерфейс помещается в файл М.h, а реализация в файл М.с (срр). Теперь, если мы хотим использовать интерфейсы, мы должны воспользоваться механизмом #include файлов. Правила хорошего тона говорят, что если появляется включение extern и, вообще, прототипы функций могут появиться только в include-файле. Это очень похоже на импорт, чем по сути и является. Другое дело, что это никак не контролируется ни языком, ни чем-то еще. Если у нас такая ситуация:

М      М1       М2

          М2

   

 Получится, что М2 появиться дважды в одной и той же единице трансляции. Поэтому хороший программист начинает  include-файл с проверки.

#ifndef __My_F__H__

#define

#endef

              /* do not add anything

Каждому include-файлу присваивается уникальное имя, некоторые среды генерируют его автоматически.

Еще одна проблема такова, что у нас крайне не структурированное пространство имен в языке С и С++. У нас есть спецификатор extern, который используется по умолчанию, который говорит, что данные имена является глобальными и видимыми везде. И есть спецификатор static, который говорит, что данное описание видимо только в пределах данного модуля.  В других ЯП, если мы импортируем два модуля, и у нас возникает конфликт, мы его разрешаем так: М1.х и М2.х доже если х является явно видимым. В случае include-файлов мы таким образом данный конфликт обойти не можем. Если одно имя использовано несколько раз, нам уже ничего не поможет. В языке С++, как и в С# (на самом деле: в C#, как в С++) есть понятие пространства имен (namespace). Отличие состоит в том, что пространство имен в С++ не является расширяемым. Это означает, что, если мы в Java, или C# могли описать пакет (пространство имен), а потом ниже к нему еще что-то дописать, то здесь этого уже нельзя. В одном файле два пространства имен с одинаковыми именами появиться не может. Пространства имен могут быть вложенными, т.е.

Namespace N1 {

    Namespace N2 {

         Class x {…};

    };

};

 Технология осталась той же самой. Если все это находится в каком-то файле М, то мы должны сделать следующее:

Include < M >

            N1::N2::x

Или вместо этого можем написать

            Using namespace N1;

Интересно, что в С++ появилось так называемое неименнованное пространство имен, т.е. пространство имен без имени.

Namespace {

  Class A{…};

  Class B{…};

};

После этого можно написать

A a = new A ( );

Это эквивалентно тому, что когда компилятор видит не именованное пространство имен, поскольку компилятор видит только один файл, он генерирует уникальное имя, переписывает все это как

Namespace unique_name {

Class A{…};

            Class B{…};

        };

  using namespace unique_name;

A a = new A ( );

 Зачем нужен так4ой механизм? Это некоторый способ описания локального для данного файла имен. Если мы описываем пространство имен, оно должно попасть внутрь интерфейса, а неименованное пространство имен – это, как бы, вложенное в наш файл пространство имен. Воспользоваться им откуда-то извне совершенно невозможно. Это эквивалентно тому, что мы опишем все с помощью идентификатора static. В новом стандарте рекомендуют, если Вы пишите заново программу, то лучше static не использовать, а пользоваться неименованным пространством имен. Все это ведет к тому, что рано или поздно в языке появиться механизм настоящей зависимой раздельной трансляции. Сейчас в языке уже есть все, что для этого надо. Осталось сделать только один согласованный шаг (все разработчики должны сделать его одновременно). Скорее всего, уже следующий стандарт языка будет содержать механизм раздельной  трансляции на уровне языка, а не на уровне среды разработки, которую каждый реализует по-своему. Правда, механизм шаблонов очень тяжело ложиться на механизм раздельной трансляции.

Глава 8.

Исключительные ситуации.

Механизм обработки исключений есть во всех современных ЯП, которые мы рассматриваем. И этот механизм идейно идентичен во всех этих языках. В качестве примера мы будем рассматривать следующие языки.

Ада, С++,  Java, C#, Delphi

Java, C#, Delphi переняли механизм обработки ИС (исключительние ситуации) из языка С++, а С++ в свою очередь опирался на язык Ада. Поэтому механизм обработки ИС в этих языка очень похожий. Самый простой он в языке Ада. Концепция ИС в-первые была предложена в языке Ада и, как мы видим, почти без исключения перешла во все современные ЯП.

ИС – это авария, некоторая ошибка в программе, которая может возникнуть по двум причинам,  во-первых, из-за ошибок в самой программе (не бывает до конца идеальных программ), во-вторых, из-за ошибок во входных данных, т.е. некоторая ситуация, которая не предусмотрена в нормальном ходе выполнения программы. Одно из определений надежного программирования – это искусство строить надежные системы из ненадежных компонент. При возникновении аварии в современном программировании надежная система может немного деградировать, но перестать работать она не может. ИС – это авария, и надежная система должна эту аварию локализовать и по возможности исправить. Понятие обработки ИС появилось довольно давно, еще в начале 60-х были языки, которые позволяли обрабатывать некоторые нехорошие ситуации. Например, в языке PLI был некоторый набор ИС

Оп имя_ИС

    Подвешивалась подпрограмма

OVERFLOW  goto <метка>

Х = А * В;

При возникновении ИС делался переход на соответствующий оператор. Если нет обработчика, то при переполнении программа просто слетала, а при обработчике, выдается диагностика об ошибке, и программа как-то разумно завершает работу (это уже наше личное дело, как реагировать на ошибку). Это очень похоже на обработку прерываний от внешних устройств. Проблема была в том, что на событие OVERFLOW вставлялся несколько свой обработчик прерываний, а мсеханизм обработки прерываний низкоуровневый, и обрабатывать ИС было достаточно неудобно. Вторым недостатком явилось то, что в качестве таких ИС фигурировало событие ЕNDFILE, если выполнялось что-то типа GET(p), и натыкались на конец файла, то возбуждалась именно вышеописанная ИС. Называть ситуацию конца файла аварийной как-то странно, потому что файлы, как правило, все-таки кончаются. Рассматривать конец файла как аварию – смешно. Здесь проблема в том, что смешиваются два понытия: ситуация, которая все равно рано или поздно наступит и действительно аварийная ситуация. До сих пор несмотря на то, что идеология ИС – это авария, некоторые программисты продолжают использовать ИС как программный механизм. Исключения нельзя использовать как стандартный прием программирования. Мы будем рассматривать ИС, как реакция на некоторую ошибку, и с этой точки зрения рассмотрим 4 аспекта:

1. Определение (как определяют исключения)

2. Возникновение

3. Распространение

4. Обработка

На современном этапе во время обработки должен выключаться нормальный порядок вычисления программы и должен запускаться несколько априори заданный ход вычисления. Надо понимать, что обычно ошибка возникает на уровне (логическом уровне программы), который совершенно не компетентен ее обработать. Например, мы хотим открыть файл, а файла не существует, то ли сетевой драйвер отключился, то ли имя файла не правильно было указано, в любом случае в том месте, где мы пытаемся открыть файл, по этому поводу ничего сказать не можем. То ли нужно переподключить сетевой драйв, то ли нужно запросить у пользователя новое имя файла, а ввод данных происходит совершенно на другом уровне программы. Т.е. в месте, где возникла ошибка не всегда есть возможность ее исправить. Этим были плохи ЯП с локальной обработкой ИС. Сейчас возбуждается ИС, и она распространяется до тех пор, пока не выйдет на уровень, который достаточно компетентен, что бы ее обработать.  Самый плохой случай, когда ни один уровень не является достаточно компетентным, тогда исключение выскакивает на самый верхний уровень, и самое разумное, что можно сделать, это завершить программу с диагностикой об ошибке. Это, конечно не подходит для надежной системы, но в целях отладки, получим диагностику. Еще одно важное свойство таково, что нормальный код выполнения программы должен быть отделен от код, который исправляет ошибки. Это потому, что в 99 случаях их 100 у нас работает только нормальный код. Программирование на С заставляет смешивать эти код. Следовательно сопровождение и отладка крайне тяжелое занятие. Современные ЯП позволяют нам структурировать, т.е. отделить текстуально код по исправлению ошибок от код, ответственного за нормального выполнения программы. Сначала посмотрим, как это реализовано в языке Ада.

Ада.

1. Определение. В Аде введено специальное ключевое слово exception. Это похоже на описание типов данных, т.е. фактически у нас с исключениями связан контретный тип данных, но его таковым не называют, потому что к нему не применимы почти никакие операции, за исключением возникновения и обработчика. Мы будем считать, что это особый тип данных, который встроен язык. Объекты типа exception мы объявляем как переменные. Например,

FILE_ERROR: exception;

С точки зрения реализации каждое исключение кодируется своим целочисленным идентификатором. Кром6е этого в языке Ада есть предопределенные исключения, например, RANGE_ERROR; мы много говорили о квазистатическом контроле. Например, у нас написано X := A(I); где I индекс, и I выходит за рамки допустимых значений. Вот тогда-то и возникает предопределенное исключение RANGE_ERROR. Не важно, что оно предопределенное, прграммист его все равно может перехватить и обработать. Важно, что не произойдет порча памяти. Кроме предопределенных исключений, пользователь может вводить свои.

2. Возникновение.  Для предопределенных исключений компилятор сам вставляет возбуждение ИС, для пользовательских – есть специальный оператор языка Ада

raise имя_исключения , например, raise FILE_ERROR;

Мы знаем, что не смогли открыть файл, но как это исправить на данном уровне не знаем, поэтому просто сигнализируем.

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

Pr P

x

 

  Pr P1

  x


     Pr P2

     x

    P3                      Статическая область видимости                     


  Pr P3

                                Динамическая область видимости

Все ЯП, которые мы рассматриваем, со статической областью видимости, а для исключений ситуация немного другая. Они обладают именно динамической областью видимости. Считается, что исключение произошло именно в программном блоке, под которым понимается, например, тело процедуры (это простейший пример блока). Иногда это тела пакета, хотя чаще под блоком понимают тело процедуры. Будем считать, что у нас есть процедура МAIN, которая вызвала процедуру Р1, которая вызвала процедуру Р32, которая вызвала процедуру Р3.

МAIN               Р1                    Р2                   Р3       

Совершенно не важно, как эти процедуры расположены друг относительно друга, то ли они имеют вложенную структуру, то ли расположены в рядок, это совершенно не важно. Считается, что блок в котором произошла ошибка – это аварийный блок,  и поэтому он завершается аварийным образом. Если в Р3 у нас произошла ошибка, то Р3 заканчивается аварийно, исключение распространяется на Р2, поскольку Р3 была вызвана из Р2. Если в Р2 не нашлось обработчика, то Р2 то же считается аварийным и распространение идет дольше вплоть до МAIN, если и там нет обработчика, то МAIN считается аварийным. Если в блоке произошла ошибка, то в него управление уже не войдет. Компилятор языка Ада смотрит за тем, чтобы после raise не было больше операторов, так как все равно до них никогда не дойдет управление. Понятно почему все эти raise заключены в условные операторы.

3. Распространение.


МAIN

     

  Р1


              Р2

                

              Р3


После каждого перехода на более высокий уровень ИС захватывает все больше и больше пространства программы.

4. Обработка и перехват.

Синтаксически это выглядит так. Любой блок может завершаться блоком обработки исключений.

Procedure P3 is

(объявление переменных)

begin

     if (…) then raise FILE_ERROR;

     endif;

exception  (это непосредственно перед end P3)

   when имя1 => ……;

  when имя2 => ……;        обработчики (здесь могт стоять любые                                                                операторы языка Ада)

  ……………………..

  when others => ……;   (ловушка последней надежды)

end P3;

Ловушки перебираются сверху вниз. Если какое-то имя совпало, значит мы перехватили ИС. Если будет где-то when FILE_ERROR, то управление перейдет на этот блок при ИС FILE_ERROR.  

procedure P2 is

(объявление переменных)

begin

  P3 ( );

  PUT (1);

Exception

…………

end P2;

Если в Р3 мы не нашли обработчика, то ошибочным считается Р2. Если и там нет обработчика, то распространиение идет дальше. ИС считается обработанной после того как ма нашли обработчик и он отработал. Если мы не знаем, как исправить ошибку, но у нас были захвачены какие-то ресурсы, то мы должны их именно сдесь освободить и  перевозбудить ИС (это делается специальным оператором raise без параметров, он может встречаться только внутри блока обработки ошибок, он говорит, что ошибка, какая бы она не была перевозбуждается и идет выше).

Procedure P3 is

X: Integer := F(I);

     P := new T;

 begin

  if (…) then raise FILE_ERROR;

  endif;

exception;

when …………….;

Вместе с этой лекцией читают "Лекция 5".

…………………

when others => unchecked deallocation;

end P3;

when others появляется либо, как последняя надежда, либо для частичного исправления ошибки.

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

К любой программе на языке Ада априори подключен блок обработки ИС.

Свежие статьи
Популярно сейчас
Почему делать на заказ в разы дороже, чем купить готовую учебную работу на СтудИзбе? Наши учебные работы продаются каждый год, тогда как большинство заказов выполняются с нуля. Найдите подходящий учебный материал на СтудИзбе!
Ответы на популярные вопросы
Да! Наши авторы собирают и выкладывают те работы, которые сдаются в Вашем учебном заведении ежегодно и уже проверены преподавателями.
Да! У нас любой человек может выложить любую учебную работу и зарабатывать на её продажах! Но каждый учебный материал публикуется только после тщательной проверки администрацией.
Вернём деньги! А если быть более точными, то автору даётся немного времени на исправление, а если не исправит или выйдет время, то вернём деньги в полном объёме!
Да! На равне с готовыми студенческими работами у нас продаются услуги. Цены на услуги видны сразу, то есть Вам нужно только указать параметры и сразу можно оплачивать.
Отзывы студентов
Ставлю 10/10
Все нравится, очень удобный сайт, помогает в учебе. Кроме этого, можно заработать самому, выставляя готовые учебные материалы на продажу здесь. Рейтинги и отзывы на преподавателей очень помогают сориентироваться в начале нового семестра. Спасибо за такую функцию. Ставлю максимальную оценку.
Лучшая платформа для успешной сдачи сессии
Познакомился со СтудИзбой благодаря своему другу, очень нравится интерфейс, количество доступных файлов, цена, в общем, все прекрасно. Даже сам продаю какие-то свои работы.
Студизба ван лав ❤
Очень офигенный сайт для студентов. Много полезных учебных материалов. Пользуюсь студизбой с октября 2021 года. Серьёзных нареканий нет. Хотелось бы, что бы ввели подписочную модель и сделали материалы дешевле 300 рублей в рамках подписки бесплатными.
Отличный сайт
Лично меня всё устраивает - и покупка, и продажа; и цены, и возможность предпросмотра куска файла, и обилие бесплатных файлов (в подборках по авторам, читай, ВУЗам и факультетам). Есть определённые баги, но всё решаемо, да и администраторы реагируют в течение суток.
Маленький отзыв о большом помощнике!
Студизба спасает в те моменты, когда сроки горят, а работ накопилось достаточно. Довольно удобный сайт с простой навигацией и огромным количеством материалов.
Студ. Изба как крупнейший сборник работ для студентов
Тут дофига бывает всего полезного. Печально, что бывают предметы по которым даже одного бесплатного решения нет, но это скорее вопрос к студентам. В остальном всё здорово.
Спасательный островок
Если уже не успеваешь разобраться или застрял на каком-то задание поможет тебе быстро и недорого решить твою проблему.
Всё и так отлично
Всё очень удобно. Особенно круто, что есть система бонусов и можно выводить остатки денег. Очень много качественных бесплатных файлов.
Отзыв о системе "Студизба"
Отличная платформа для распространения работ, востребованных студентами. Хорошо налаженная и качественная работа сайта, огромная база заданий и аудитория.
Отличный помощник
Отличный сайт с кучей полезных файлов, позволяющий найти много методичек / учебников / отзывов о вузах и преподователях.
Отлично помогает студентам в любой момент для решения трудных и незамедлительных задач
Хотелось бы больше конкретной информации о преподавателях. А так в принципе хороший сайт, всегда им пользуюсь и ни разу не было желания прекратить. Хороший сайт для помощи студентам, удобный и приятный интерфейс. Из недостатков можно выделить только отсутствия небольшого количества файлов.
Спасибо за шикарный сайт
Великолепный сайт на котором студент за не большие деньги может найти помощь с дз, проектами курсовыми, лабораторными, а также узнать отзывы на преподавателей и бесплатно скачать пособия.
Популярные преподаватели
Добавляйте материалы
и зарабатывайте!
Продажи идут автоматически
5167
Авторов
на СтудИзбе
437
Средний доход
с одного платного файла
Обучение Подробнее