лекция 18 (Языки программирования (лекции) (2008))
Описание файла
Файл "лекция 18" внутри архива находится в папке "Языки программирования (лекции) (2008)". Документ из архива "Языки программирования (лекции) (2008)", который расположен в категории "". Всё это находится в предмете "языки программирования" из 7 семестр, которые можно найти в файловом архиве МГУ им. Ломоносова. Не смотря на прямую связь этого архива с МГУ им. Ломоносова, его также можно найти и в других разделах. .
Онлайн просмотр документа "лекция 18"
Текст из документа "лекция 18"
Языки программирования.
Лекция 17.
ИС в Java, C++, Delphi.
При обработке ИС мы выделяем 4 аспекта:
-
определение
-
возбуждение
-
распространение
-
обработка
На примере яп Ада мы рассмотрели принципы обработки ИС, эти принципы заимствованы современными яп .
- exception (псевдотип, объекты которого являются ИС (6 предопределенных), пользователь может сам определить свои константы)- определение
- raise имя_ИС - возбуждение
-принцип завершения(динамической ловушки)- распространение
-when имя1 {| имя}=> операторы (среди операторов может быть и raise--> перевозбуждение данной ИС), например блок не знает, как окончательно подавить данную ИС-обработка
Что же с языками Си++, Delphi, Java, Cи#?
Обработка исключений тотальна(как и в Аде), мы не можем написать программу, в которой не было бы ИС.
В Java нагрузка на обработку ИС ложится на ядро виртуальной машины.
В Cи# - на ядро ОС,
В Дельфи приходится эту поддержку "таскать за собой".
Эти яп -тотальны, т.к в Java и Дельфи программирование в терминах одного языка и одной ОС, в C# в терминах общей унифицированной системы типов.
Во всех этих языках есть единый тип, от которых происходят все исключения.
1.Определение
В C++ не любые аварии можно выразить в терминах С++ (так как эти ИС могут быть вызваны подпрограммой на языке С, на ассемблере,...).ИС в С++ ассоциируются только с пользовательскими исключениями, предопределенных ИС нет. В некоторых компиляторах (Microsoft) есть возможность обертывать структурированные ИС в стандартные С++ исключения, происходящие от исключений , порожденные ОС.
В С++ подход следующий:
-В С++ ИС - это произвольный тип данных.
Так как в C#, Java и Дельфи всё унаследовано от класса Object, поэтому исключения наследуются от специальных типов данных.
Java - Throwable
Cи# - Exception
Delphi - TException
Все эти типы содержат строку типа String, содержащую описание ИС(по крайней мере текст- сообщение об ошибке и методы для вывода его на экран ).
в Аде при вызове rase программист знает только идентификатор исключения, вся остальная инф-ия скрыта , в данной ситуации мы можем сами запихивать необходимую инф-ию в класс.
Программист на С++ начинал работу с исключениями с того, что сам писал свой класс типа CException, в котором определял, например, описание ИС, имя файла и номер строки, где произошла эта ИС. В C#, D, J даже этого делать не надо - всё уже есть. Т.к. система встроена в язык (наличие стеков вызова, откуда программист может узнать, где произошла ошибка, т.о. при завершении программы по необработанной ИС, можно, по крайней мере, распечатать стек вызовов).
2. Возбуждение ИС
В С++ есть единственный способ возбуждения ИС - это оператор throw.
Почему Страуструп не стал использовать ключевое слово raise (так как он за основу брал исключения языка Ада )?
Т.к. в 80-х гг в основном программируют на С. Но в Юниксе raise - системный вызов посылки сигнала самому себе и это сидит в стандартном заголовочном файле юникса. А Страуструп не хотел вводить ключевые слова, которые конфликтовали бы с чем-то из юникса.
throw выражение- тип выражения определяет тип ИС
throw "error" - с этим исключением связывается тип char *
throw CException(); - с типом CException, предполагается ,что соответствующий класс существует
throw new MathError; - с типом MathError *, соответственно ИС должна отводится в особой памяти, т.к. проблема может быть в нехватке динамической памяти.
Поддержка ИС довольно требовательна к ресурсам.
Вызов ИС
(т.к. в этих ИС - объекты классы--> располагаются в динамической памяти--> любые исключения- ссылки на объекты соответ исключения):
Java - throw new Exception();
C# - throw new Exception();
Delphi - raise TException.Create();
Лучше всего создать свой тип, производный от данных.
В этих яп предопределенные ИС содержатся в библиотеке, так в Дельфи ИС, порожденные ОС, можно обернуть в специальный объект типа TException.
3. Распространение ИС:
Во всех языках, кроме языка Ада, принят принцип динамической ловушки или стратегия завершения. Весь блок, в котором произошла ИС считается аварийным и в конце блока ищутся ловушки
...
f() {
...
throw new Err();
}
Если внутри блока, который содержит throw new Err(); то блок, который вызвал данный считается аварийным, т.е.
становится эквивалентным:
{
f();
throw new Err();// аварийная ситуация
}
И т.д. пока не будет найдена ловушка.
В C++ есть 2 ситуации. Можно повесить обработчик terminate вызывается, если выполнение программы заканчивается ели не найден обработчик вызванной ИС. Вызов terminate = вызов обработчика ситуации, когда произошла авария, а обработчика не нашлось.
typedef void (*pvf)(void)(процедура без параметров)
pvf set_terminate(pvf new_term_handler) - установка обработчика ИС. Возвращает предыдущую реакцию.
В норме, при вызове она выдает сообщение о том, что произошла ИС, которая в программе не обработана, и программа абортит.
terminate_handler вызывается, когда стек свернулся до функции main и максимум, что мы можем сделать - это выдать сообщение и перезапустить процесс, так что многого от этого обработчика ожидать нельзя.
В этих яп все зависит от среды, в которой вы программируете, если вы программируете в событийной среде у вас есть главный цикл обработки сообщений . Если вы поставите в нем ловушку, то вы будете перехватывать все сообщения , и у вас есть выбор: либо закончить программу ,либо попытаться заново войти в цикл, проигнорировав ИС.
Существенное отличие С++ от 3-х остальных яп, рассматриваемых в этой лекции:
В С++ есть специальный термин- свертка стека .
Пример:
Стек программы:
среда ссылок ( локальные переменные и формальные параметры) для main
среда ссылок для f();
некий блок внутри, которого произошло- throw();
Если в блоке не обнаружено ловушек, то процесс распространения сводится к тому, что вызов ф-ии f() считается ошибочным и в этом блоке отыскивается ловушек, а блок схлопывается - вызов деструкторов всех локальных объектов, затем выкидывание из памяти.
Пример с курсором:
{
CWaitCursor waitc;// захват курсора- песочные часы
operation();
}
Даже если операция не будет выполнена, то выход из блока всё равно будет, будет вызван деструктор, в котором мы сможем вернуть нормальный вид курсора(гарантировано сверткой текста).
В Аде нет понятия локальных объектов и нет гарантии, что локальный объект будет правильно уничтожен.
В языке Ада есть конструкция, которая позволяет программным способом это исправить.
В остальных 3-х яп все объекты располагаются только в динамической памяти --> уходят они явным образом--> нет гарантий вызова деструктора, т.к. нет понятия локальных объектов --> свертка стека- это присвоение ссылка nil.В соответствующих яп должна быть конструкция, которая позволяет программным способом очистить свертку стека .
4. Обработка ИС
Где-то программист должен поставить ловушку чтобы обрабатывать ИС.
Ада:
begin
when имя => оператора списков сколько угодно
end
Минимальная обработка(Страуструп):
оператор список_ловушек;
Си++
Страуструп решил, что единицей программной конструкции, где может быть поймано исключение - это блок, так называемый try-блок:
try {
операторы// объявление переменных- оператор!
}
catch (тип1 имя 1) { ... }
catch (тип2 имя 2) { ... }// имя может отсутствовать , т.к. ИС ловится по типу.
...
catch (...) { ... } // ловит все остальные ИС, должна быть последней
Тут catch выглядит как вызов процедуры с соответствующими параметрами.
void f (T /*x*/)
{ ... }
Почему нам не требовать описывать имя? Потому что оно объявлено и не используется, и многие компиляторы на это ругаются и правильно делают .Функции обратного вызова-callback-реакции на события, параметры нам не нужны, но по соответствию типов мы описывать их должны. При обработке события, нам нужно знать только тип.
Просмотр идет сверху вниз.
Среди операторов try-блока может быть оператор throw T(); и при его выполнении производится сопоставление типа Т и типов в ловушках catch. Сначала ищется полное совпадение типа, потом идут неявные преобразования (от производного типа к базовому).
Если есть типы:
Exception => UserException
try {
....
}
catch (Exception *p) {...}
catch (UserException *p) {...}
Exeption -ok!
Если у нас будет возбуждена ИС с типом UserException, то всё равно проработает первая ловушка, а второй обработчик никогда не будет вызван. Чтобы всё работало правильно, эти обработчики надо поменять местами.
В Cи# и Java отсутствует ловушка catch(...), потому что каждое исключение порождено от типа Throwable и достаточно поставить ловушку только на него.
Рассмотрим Java, Си#:
-try блок, внутри которого может встречаться throw;
-список ловушек(catch);
- finally;
Отличие от Си++:
- отсутствует обработчик catch (...)
- добавляется в try-блок обработчик finally, который находится в конце этого блока.
Блок finally выполняется всегда, как бы блок try не завершился. В таких блоках очень хорошо размещать освобождение ресурсов (в Си++ это выполняли деструкторы).
Java:
T x = null;
try {
x = new T();
...
} finally {
if (x != null)
освободить x;
}
В языке Java, если объект забирает какие-то ресурсы( помимо динамической памяти), то обязательно их надо освобождать в блоке finally.
try- блок в Delphi(деструкторы должны вызываться явно):
try
x:=Create;
finally
x.Free;
end;
Си#:
IDisposable- интерфейс, который состоит из 1 метода:
Dispose(); явный вызов этого метода в языке С#, если мы явно хотим освободить ресурс.
Причем после использования данного интерфейса автоматический сборщик мусора не вызывается для этого объекта.
В Си# есть специальная конструкция using:
using (T x = new T()) {
...
}
И если x != null, то для не вызывается Dispose();
Но если х не поддерживает этот Dispose или надо освободить несколько объектов, то конструкция try-finally оказывается полезна.
2 замечания:
1). Синтаксис Дельфи (семантика остаётся такой же):
try
операторы
except
список ловушек
end;
он не имеет блок finally, поэтому если мы хотим его использовать, то надо писать:
try
try
операторы
except
список ловушек
end;
finally
операторы;
end;
Список ловушек имеет вид:
(on имя: тип do операторы)
несколько операторов-begin...end
тип- TException или производные от него.
2).Объявляемые(проверяемые) ИС (С++ и Java-исключения).
тип имя (операторы) throw (список_типов)
{...}
в общем случае
class X {
void f() throw (A,B)
}
Таким образом мы объявляем, что в теле f() могут возбуждаться необработанные исключения типов А и В (и ТОЛЬКО этих типов).
Для чего это сделано:
пусть внутри f() всё же появилась ИС типа С и где-то вызывается эта функция:
X *p;
p->f()
обработчиков ИС типа С в этом блоке скорее всего не будет, т.к. ожидаются ТОЛЬКО А и В, и, как следствие, возникает необработанная ИС и программа должна сразу завершаться (ошибка в коде- непредусмотренная ошибка), но мы дойдём до функции main и будет сообщение, что в main появилась такая ИС(т.к. нет трассировки).
Т.о. чем раньше мы отреагируем на необработанное исключение , тем лучше.
Способы завершения программы при необработанном исключении:
terminate()-система вызывает, когда необработанное исключение доходит до main, что вообще говоря не обязательно, т.к. после выхода из самого внешнего try-блока ловушек не будет можно вызывать terminate().