Лекции.ИНФО


Abnormal program termination



Функции exit() и abort()

Функции exit() и abort() входят в состав стандартной библиотеки C++ и часто используются в программировании на C++. Обе они обеспечивают завершение программы, но по-разному.

Вызов функции exit() немедленно приводит к "правильному" прекращению программы. ("Правильное" окончание означает выполнение стандартной последовательности действий по завершению работы.) Обычно этот способ завершения работы используется для остановки программы при возникновении неисправимой ошибки, которая делает дальнейшее ее выполнение бессмысленным или опасным. Для использования функции exit() требуется включить в программу заголовок <cstdlib>. Ее прототип выглядит так.

void exit(int status);

Поскольку функция exit() вызывает немедленное завершение программы, она не передает управление вызывающему процессу и не возвращает никакого значения. Тем не менее вызывающему процессу в качестве кода завершения передается значение параметра status. По соглашению нулевое значение параметра status говорит об успешном окончании работы программы. Любое другое его значение свидетельствует о завершении программы по ошибке. Для индикации успешного окончания можно также использовать константу EXIT_SUCCESS, а для индикации ошибки— константу EXIT_FAILURE. Эти константы определены в заголовке <cstdlib>.

Прототип функции abort() выглядит так:

void abort();

Аналогично exit() функция abort() вызывает немедленное завершение программы. Но в отличие от функции exit() она не возвращает операционной системе никакой информации о статусе завершения и не выполняет стандартной ("правильной") последовательности действий при остановке программы. Для использования функции abort() требуется включить в программу заголовок <cstdlib>. Функцию abort() можно назвать аварийным "стоп-краном" для С++-программы. Ее следует использовать только после возникновения неисправимой ошибки.

Последнее сообщение об аварийном завершении программы (Abnormal program termination) может отличаться от приведенного в результатах выполнения предыдущего примера. Это зависит от используемого вами компилятора.

Исключение, сгенерированное функцией, вызванной из try-блока, может быть перехвачено этим же try-блоком. Рассмотрим, например, следующую вполне корректную программу.

/* Генерирование исключения из функции, вызываемой из try-блока.

*/

#include <iostream>

using namespace std;

Void Xtest(int test)

{

 cout << "В функции Xtest(), значение test равно: "<< test << "";

 if(test) throw test;

}

Int main()

{

 cout << "НАЧАЛО";

 try {

  // начало try-блока

  cout << "В trу-блоке";

  Xtest (0);

  Xtest (1);

  Xtest (2);

 }

 catch (int i) {

  // перехват ошибки

  cout << "Перехват исключения. Его значение равно: ";

  cout << i << "";

 }

 cout << "КОНЕЦ";

 return 0;

}

Эта программа генерирует такие результаты.

НАЧАЛО В try-блоке

В функции Xtest(), значение test равно: 0

В функции Xtest(), значение test равно: 1

Перехват исключения. Его значение равно: 1

КОНЕЦ

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

#include <iostream>

using namespace std;

/* Функционирование блоков try/catch возобновляется при каждом входе в функцию.

*/

Void Xhandler(int test)

{

 try {

  if(test) throw test;

 }

 catch(int i) {

  cout << "Перехват! Исключение №: " << i << '';

 }

}

Int main()

{

 cout << "HAЧАЛО";

 Xhandler (1);

 Xhandler (2);

 Xhandler (0);

 Xhandler (3);

 cout << "КОНЕЦ";

 return 0;

}

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

НАЧАЛО

Перехват! Исключение №:1

Перехват! Исключение №:2

Перехват! Исключение №:3

КОНЕЦ

Как видите, программа сгенерировала три исключения. После каждого исключения функция Xhandler() передавала управление в функцию main(). Когда она снова вызывалась, возобновлялась и обработка исключения.

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

Перехват исключений классового типа

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

// Использование класса исключений.

#include <iostream>

#include <cstring>

using namespace std;

class MyException {

 public:

  char str_what[80];

  MyException() { *str_what =0; }

  MyException(char *s) { strcpy(str_what, s);}

};

Int main()

{

 int a, b;

 try {

  cout << "Введите числитель и знаменатель: ";

   cin >> а >> b;

  if( !b) throw MyException("Делить на нуль нельзя!");

  else

   cout << "Частное равно " << a/b << "";

 }

 catch (MyException e) {

  // перехват ошибки

  cout << e.str_what << "";

 }

 return 0;

}

Вот один из возможных результатов выполнения этой программы.

Введите числитель и знаменатель: 10 0

Делить на нуль нельзя!

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

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

Использование нескольких catch-инструкций

Как упоминалось выше, с try-блоком можно связывать не одну, а несколько catch-инструкций. В действительности именно такая практика и является обычной. Но при этом все catch-инструкции должны перехватывать исключения различных типов. Например, в приведенной ниже программе обеспечивается перехват как целых чисел, так и указателей на символы.

#include <iostream>

using namespace std;

// Здесь возможен перехват исключений различных типов.

Void Xhandler(int test)

{

 try {

  if(test) throw test;

  else throw "Значение равно нулю.";

 }

 catch (int i) {

 cout << "Перехват! Исключение №: " << i << '';

 }

 catch(char *str) {

  cout << "Перехват строки: ";

  cout << str << '';

 }

}

Int main()

{

 cout << "НАЧАЛО";

 Xhandler(1);

 Xhandler(2);

 Xhandler(0);

 Xhandler(3);

 cout << "КОНЕЦ";

 return 0;

}

Эта программа генерирует такие результаты.

НАЧАЛО

Перехват! Исключение №: 1

Перехват! Исключение №: 2

Перехват строки: Значение равно нулю.

Перехват! Исключение №: 3

КОНЕЦ

Как видите, каждая catch-инструкция отвечает только за исключение "своего" типа. В общем случае catch-выражения проверяются в порядке следования, и выполняется только тот catch-блок, в котором тип заданного исключения совпадает с типом сгенерированного исключения. Все остальные catch-блоки игнорируются.

Перехват исключений базового класса

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

// Перехват исключений базовых и производных типов.

#include <iostream>

using namespace std;

class В {

};

class D: public В {

};

Int main()

{

 D derived;

 try {

  throw derived;

 }

 catch(B b) {

  cout << "Перехват исключения базового класса.";

 }

 catch(D d) {

 cout << "Этот перехват никогда не произойдет.";

 }

 return 0;

}

Поскольку здесь объект derived — это объект класса D, который выведен из базового класса В, то исключение типа derived будет всегда перехватываться первым catch-выражением; вторая же catch-инструкция при этом никогда не выполнится. Одни компиляторы отреагируют на такое положение вещей предупреждающим сообщением. Другие могут выдать сообщение об ошибке. В любом случае, чтобы исправить ситуацию, достаточно поменять порядок следования этих catch-инструкций на противоположный.

Варианты обработки исключений

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

Перехват всех исключений

Иногда имеет смысл создать обработчик для перехвата всех исключений, а не исключений только определенного типа. Для этого достаточно использовать такой формат catch-блока.

catch (...) {

 // Обработка всех исключений

}









Читайте также:

Последнее изменение этой страницы: 2016-03-17; Просмотров: 189;


lektsia.info 2017 год. Все права принадлежат их авторам! Главная