Ситуация это любая ошибка работы программы

Эта глава посвящена обработке исключительных ситуаций. Исключительная ситуация (или исключение) — это ошибка, которая возникает во время выполнения программы. Используя С++-подсистему обработки исключительных ситуаций, с такими ошибками вполне можно справляться. При их возникновении во время работы программы автоматически вызывается так называемый обработчик исключений. Теперь программист не должен обеспечивать проверку результата выполнения каждой конкретной операции или функции вручную. В этом-то и состоит принципиальное преимущество системы обработки исключений, поскольку именно она «отвечает» за код обработки ошибок, который прежде приходилось «вручную» вводить в и без того объемные программы.

В этой главе мы также возвращаемся к С++-операторам динамического распределения памяти: new и delete. Как разъяснялось выше в этой книге, если оператор new не может выделить требуемую память, он генерирует исключение. И здесь мы узнаем, как именно обрабатывается такое исключение. Кроме того, вы научитесь перегружать операторы new и delete, что позволит вам определять собственные схемы выделения памяти.

Основы обработки исключительных ситуаций

Обработка исключений — это системные средства, с помощью которых программа может справиться с ошибками времени выполнения.

Управление С++-механизмом обработки исключений зиждется на трех ключевых словах: try, catch и throw. Они образуют взаимосвязанную подсистему, в которой использование одного из них предполагает применение другого. Для начала будет полезно получить общее представление о роли, которую они играют в обработке исключительных ситуаций. Если кратко, то их работа состоит в следующем. Программные инструкции, которые вы считаете нужным проконтролировать на предмет исключений, помещаются в try-блок. Если исключение (т.е. ошибка) таки возникает в этом блоке, оно дает знать о себе выбросом определенного рода информации (с помощью ключевого слова throw). Это выброшенное исключение может быть перехвачено программным путем с помощью catch-блока и обработано соответствующим образом. А теперь подробнее.

Инструкция throw генерирует исключение, которое перехватывается catchинструкцией.

Итак, код, в котором возможно возникновение исключительных ситуаций, должен выполняться в рамках try-блока. (Любая функция, вызываемая из этого try-блока, также подвергается контролю.) Исключения, которые могут быть выброшены контролируемым кодом, перехватываются catch-инструкцией, непосредственно следующей за try-блоком, в котором фиксируются эти «выбросы» исключений. Общий формат try— и catch-блоков выглядит так.

try {

// try-блок (блок кода, подлежащий проверке на наличие ошибок)

}

catch (type1 arg) {

// catch-блок (обработчик исключения типа type1)

}

catch {type2 arg) {

// catch-блок (обработчик исключения типа type2)

}

catch {type3 arg) {

// catch-блок (обработчик исключения типа type3)

}

// …

catch (typeN arg) {

// catch-блок (обработчик исключения типа typeN)

}

Блок try должен содержать код, который, по вашему мнению, должен проверяться на предмет возникновения ошибок. Этот блок может включать лишь несколько инструкций некоторой функции либо охватывать весь код функции main() (в этом случае, по сути, «под колпаком» системы обработки исключений будет находиться вся программа).

После «выброса» исключение перехватывается соответствующей инструкцией catch, которая выполняет его обработку. С одним try-блоком может быть связана не одна, а несколько catch-инструкций. Какая именно из них будет выполнена, определяется типом исключения. Другими словами, будет выполнена та catch-инструкция, тип исключения которой (т.е. тип данных, заданный в catch-инструкции) совпадает с типом сгенерированного исключения (а все остальные будут проигнорированы). После перехвата исключения параметр arg примет его значение. Таким путем могут перехватываться данные любого типа, включая объекты классов, созданных программистом.

Чтобы исключение было перехвачено, необходимо обеспечить его «выброс» в try-блоке.

Общий формат инструкции throw выглядит так:

throw exception;

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

На заметку. Если в программе обеспечивается «выброс» исключения, для которого не предусмотрена соответствующая catch-инструкция, произойдет аварийное завершение программы, вызываемое стандартной библиотечной функцией terminate(). По умолчанию функция terminate() вызывает функцию abort() для остановки программы, но при желании можно определить собственный обработчик ее завершения. За подробностями относительно обработки этой ситуации следует обратиться к документации, прилагаемой к вашему компилятору.

Рассмотрим простой пример обработки исключений средствами языка C++.

// Простой пример обработки исключений.

#include <iostream>

using namespace std;

int main()

{

cout << «HAЧAЛО\n»;

try {

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

cout << «В trу-блоке\n»;

throw 99; // генерирование ошибки

cout << «Эта инструкция не будет выполнена.»;

}

catch (int i) {

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

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

cout << i << «\n»;

}

cout << «КОНЕЦ»;

return 0;

}

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

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

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

КОНЕЦ

Рассмотрим внимательно код этой программы. Как видите, здесь try-блок содержит три инструкции, а инструкция catch(int i) предназначена для обработки исключения целочисленного типа. В этом try-блоке выполняются только две из трех инструкций: cout и throw. После генерирования исключения управление передается catch-выражению, при этом выполнение try-блока прекращается. Необходимо понимать, что catch-инструкция не вызывается, а просто с нее продолжается выполнение программы после «выброса» исключения. (Стек программы автоматически настраивается в соответствии с создавшейся ситуацией.) Поэтому cout-инструкция, следующая после throw-инструкции, никогда не выполнится.

После выполнения catch-блока управление программой передается инструкции, следующей за этим блоком. Поэтому ваш обработчик исключения должен исправить ошибку, вызвавшую его возникновение, чтобы программа могла нормально продолжить выполнение. В случаях, когда ошибку исправить нельзя, catch-блок обычно завершается обращением к функциям exit() или abort(). (Функции exit() и abort() описаны в разделе «Копнем глубже» ниже в этой главе.)

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

// Этот пример работать не будет.

#include <iostream>

using namespace std;

int main()

{

cout << «НАЧАЛО\n»;

try {

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

cout << «В trу-блоке\n»;

throw 99; // генерирование ошибки

cout << «Эта инструкция не будет выполнена.»;

}

catch (double i) {

// Перехват исключения типа int не состоится.

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

cout << i << «\n»;

}

cout << «КОНЕЦ»;

return 0;

}

Такие результаты выполнения этой программы объясняются тем, что исключение целочисленного типа не перехватывается инструкцией catch (double i).

НАЧАЛО

В try-блоке

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 << «\n»;

if(test) throw test;

}

int main()

{

cout << «НАЧАЛО\n»;

try {

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

cout << «В trу-блоке\n»;

Xtest (0);

Xtest (1);

Xtest (2);

}

catch (int i) {

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

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

cout << i << «\n»;

}

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 << ‘\n’;

}

}

int main()

{

cout << «HAЧАЛО\n «;

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 << «\n»;

}

catch (MyException e) {

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

cout << e.str_what << «\n»;

}

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 << ‘\n’;

}

catch(char *str) {

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

cout << str << ‘\n’;

}

}

int main()

{

cout << «НАЧАЛО\n»;

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 << «Перехват исключения базового класса.\n»;

}

catch(D d) {

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

}

return 0;

}

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

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

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

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

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

catch (…) {

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

}

Здесь заключенное в круглые скобки многоточие обеспечивает совпадение с любым типом данных.

Использование формата catch(…) иллюстрируется в следующей программе.

// В этой программе перехватываются исключения всех типов.

#include <iostream>

using namespace std;

void Xhandler(int test)

{

try {

if(test==0) throw test; // генерирует int-исключение

if(test==1) throw ‘a’; // генерирует char-исключение

if(test==2) throw 123.23; // генерирует double-исключение

}

catch (…) { // перехват всех исключений

cout << «Перехват!\n»;

}

}

int main()

{

cout << «НАЧАЛО\n»;

Xhandler (0);

Xhandler (1);

Xhandler (2);

cout << «КОНЕЦ»;

return 0;

}

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

НАЧАЛО

Перехват!

Перехват!

Перехват!

КОНЕЦ

Как видите, все три throw-исключения перехвачены с помощью одной-единственной

catch-инетрукции.

Зачастую имеет смысл использовать инструкцию catch(…) в качестве последнего «рубежа» catch-последовательности. В этом случае она обеспечивает перехват исключений «всех остальных» типов (т.е. не предусмотренных предыдущими catch-выражениями). Например, рассмотрим еще одну версию предыдущей программы, в которой явным образом обеспечивается перехват исключений целочисленного типа, а перехват всех остальных возможных исключений «взваливается на плечи» инструкции catch(…).

/* Использование формата catch (…) в качестве варианта «все остальное».

*/

#include <iostream>

using namespace std;

void Xhandler(int test)

{

try {

if(test==0) throw test; // генерирует int-исключение

if(test==1) throw ‘a’; // генерирует char-исключение

if(test==2) throw 123.23; // генерирует double-исключение

}

catch(int i) {

// перехватывает int-исключение

cout << «Перехват » << i << ‘\n’;

}

catch(…) {

// перехватывает все остальные исключения

cout << «Перехват-перехват!\n»;

}

}

int main()

{

cout << «НАЧАЛО\n»;

Xhandler(0);

Xhandler(1);

Xhandler(2);

cout << «КОНЕЦ»;

return 0;

}

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

НАЧАЛО

Перехват 0

Перехват-перехват!

Перехват-перехват!

КОНЕЦ

Как подтверждает этот пример, использование формата catch(…) в качестве «последнего оплота» catch-последовательности— это удобный способ перехватить все исключения, которые вам не хочется обрабатывать в явном виде. Кроме того, перехватывая абсолютно все исключения, вы предотвращаете возможность аварийного завершения программы, которое может быть вызвано каким-то непредусмотренным (а значит, необработанным) исключением.

Ограничения, налагаемые на тип исключений, генерируемых функциями

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

тип имя_функции(список_аргументов) throw(список_имен_типов)

{

// . . .

}

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

На заметку. При попытке сгенерировать исключение, которое не поддерживается функцией, вызывается стандартная библиотечная функция unexpected(). По умолчанию она вызывает функцию abort(), которая обеспечивает аварийное завершение программы. Но при желании можно задать собственный обработчик процесса завершения. За подробностями обращайтесь к документации, прилагаемой к вашему компилятору.

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

/* Ограничение типов исключений, генерируемых функцией.

*/

#include <iostream>

using namespace std;

/* Эта функция может генерировать исключения только типа int, char и double.

*/

void Xhandler(int test) throw(int, char, double)

{

if(test==0) throw test; // генерирует int-исключение

if(test==1) throw ‘a’; // генерирует char-исключение

if(test==2) throw 123.23; // генерирует double-исключение

}

int main()

{

cout << «НАЧАЛО\n»;

try {

Xhandler(0); // Попробуйте также передать функции Xhandler() аргументы 1 и 2.

}

catch(int i) {

cout << «Перехват int-исключения.\n»;

}

catch(char c) {

cout << «Перехват char-исключения.\n»;

}

catch(double d) {

cout << «Перехват double-исключения.\n»;

}

cout << «КОНЕЦ»;

return 0;

}

В этой программе функция Xhandler() может генерировать исключения только типа int, char и double. При попытке сгенерировать исключение любого другого типа произойдет аварийное завершение программы (благодаря вызову функции unexpected()). Чтобы убедиться в этом, удалите из throw-списка, например, тип int и перезапустите программу.

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

Следующее изменение помешает функции Xhandler() генерировать любые изменения.

// Эта функция вообще не может генерировать исключения!

void Xhandler(int test) throw()

{

/* Следующие инструкции больше не работают. Теперь они могут вызвать лишь аварийное завершение программы. */

if(test==0) throw test;

if(test==1) throw ‘a’;

if(test==2) throw 123.23;

}

На заметку. На момент написания этой книги среда Visual C++ не обеспечивала для функции запрет генерировать исключения, тип которых не задан в throw-выражении. Это говорит о нестандартном поведении данной среды. Тем не менее вы все равно можете задавать «ограничивающее» throw-выражение, но оно в этом случае будет играть лишь уведомительную роль.

Повторное генерирование исключения

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

одном аспекте исключения, а второй — о другом. Исключение можно повторно сгенерировать только в catch-блоке (или в любой функции, вызываемой из этого блока). При повторном генерировании исключение не будет перехватываться той же catch-инструкцией. Оно распространится на ближайшую try/catch-последовательность.

Повторное генерирование исключения демонстрируется в следующей программе (в данном случае повторно генерируется тип char *).

// Пример повторного генерирования исключения.

#include <iostream>

using namespace std;

void Xhandler()

{

try {

throw «Привет»; // генерирует исключение типа char *

}

catch(char *) { // перехватывает исключение типа char *

cout << «Перехват исключения в функции Xhandler.\n»;

throw; // Повторное генерирование исключения типа char *, которое будет перехвачено вне функции Xhandler.

}

}

int main()

{

cout << «НАЧАЛО\n»;

try {

Xhandler();

}

catch(char *) {

cout << «Перехват исключения в функции main().\n»;

}

cout << «КОНЕЦ»;

return 0;

}

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

НАЧАЛО

Перехват исключения в функции Xhandler.

Перехват исключения в функции main().

КОНЕЦ

Обработка исключений, сгенерированных оператором new

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

Для начала необходимо отметить, что в этом разделе описывается поведение оператора new в соответствии со стандартом C++. Как было отмечено в главе 9, действия, выполняемые системой при неуспешном использовании оператора new, с момента изобретения языка C++ изменялись уже несколько раз. Сначала оператор new возвращал при неудаче значение null. Позже такое поведение было заменено генерированием исключения. Кроме того, несколько раз менялось имя этого исключения. Наконец, было решено, что оператор new будет генерировать исключения по умолчанию, но в качестве альтернативного варианта он может возвращать и нулевой указатель. Следовательно, оператор new в разное время был реализован различными способами. И хотя все современные компиляторы реализуют оператор new в соответствии со стандартом C++, компиляторы более «почтенного» возраста могут содержать отклонения от него. Если приведенные здесь примеры программ не работают с вашим компилятором, обратитесь к документации, прилагаемой к компилятору, и поинтересуйтесь, как именно он реализует

функционирование оператора new.

Согласно стандарту C++ при невозможности удовлетворить запрос на выделение памяти, требуемой оператором new, генерируется исключение типа bad_alloc. Если ваша программа не перехватит его, она будет досрочно завершена. Хотя такое поведение годится для коротких примеров программ, в реальных приложениях необходимо перехватывать это исключение и разумно обрабатывать его. Чтобы получить доступ к исключению типа bad_alloc, нужно включить в программу заголовок <new>.

Рассмотрим пример использования оператора new, заключенного в try/catch-блок для отслеживания неудачных результатов запроса на выделение памяти.

// Обработка исключений, генерируемых оператором new.

#include <iostream>

#include <new>

using namespace std;

int main()

{

int *p, i;

try {

p = new int[32]; // запрос на выделение памяти для 32элементного int-массива

}

catch (bad_alloc ха) {

cout << «Память не выделена.\n»;

return 1;

}

for(i=0; i<32; i++) p[i] = i;

for(i=0; i<32; i++ ) cout << p[i] << » «;

delete [] p; // освобождение памяти

return 0;

}

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

Альтернативная форма оператора new — nothrow

Стандарт C++ при неудачной попытке выделения памяти вместо генерирования исключения также позволяет оператору new возвращать значение null. Эта форма использования оператора new особенно полезна при компиляции старых программ с применением современного С++-компилятора. Это средство также очень полезно при замене вызовов функции malloc() оператором new. (Это обычная практика при переводе С- кода на язык C++.) Итак, этот формат оператора new выглядит следующим образом.

p_var = new(nothrow) тип;

Здесь элемент p_var— это указатель на переменную типа тип. Этот nothrow-формат оператора new работает подобно оригинальной версии оператора new, которая использовалась несколько лет назад. Поскольку оператор new (nothrow) возвращает при неудаче значение null, его можно «внедрить» в старый код программы, не прибегая к обработке исключений. Однако в новых программах на C++ все же лучше иметь дело с исключениями.

В следующем примере показано, как используется альтернативный вариант new (nothrow). Нетрудно догадаться, что перед вами вариация на тему предыдущей программы.

// Использование nothrow-версии оператора new.

#include <iostream>

#include <new>

using namespace std;

int main()

{

int *p, i;

p = new(nothrow) int[32]; // использование nothrow-версии

if(!p) {

cout << «Память не выделена.\n»;

return 1;

}

for(i=0; i<32; i++) p[i] = i;

for(i=0; i<32; i++ ) cout << p[i] << » «;

delete [] p; // освобождение памяти

return 0;

}

Здесь при использовании nothrow-версии после каждого запроса на выделение памяти необходимо проверять значение указателя, возвращаемого оператором new.

Перегрузка операторов new и delete

Поскольку new и delete — операторы, их также можно перегружать. Несмотря на то что перегрузку операторов мы рассматривали в главе 13, тема перегрузки операторов new и delete была отложена до знакомства с темой исключений, поскольку правильно перегруженная версия оператора new (та, которая соответствует стандарту C++) должна в случае неудачи генерировать исключение типа bad_alloc. По ряду причин вам имеет смысл создать собственную версию оператора new. Например, создайте процедуры выделения памяти, которые, если область кучи окажется исчерпанной, автоматически начинают использовать дисковый файл в качестве виртуальной памяти. В любом случае реализация перегрузки этих операторов не сложнее перегрузки любых других.

Ниже приводится скелет функций, которые перегружают операторы new и delete.

// Выделение памяти для объекта.

void *operator new(size_t size)

{

/* В случае невозможности выделить память генерируется исключение типа bad_alloc. Конструктор вызывается автоматически. */

return pointer_to_memory;

}

// Удаление объекта.

void operator delete(void *p)

{

/* Освобождается память, адресуемая указателем р. Деструктор вызывается автоматически. */

}

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

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

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

Чтобы выделить память для массива объектов, а затем освободить ее, необходимо использовать следующие форматы операторов new и delete.

// Выделение памяти для массива объектов.

void *operator new[](size_t size)

{

/* В случае невозможности выделить память генерируется исключение типа bad_alloc. Каждый конструктор вызывается автоматически. */

return pointer_to_memory;

}

// Удаление массива объектов.

void operator delete[](void *p)

{

/* Освобождается память, адресуемая указателем р. При этом автоматически вызывается деструктор для каждого элемента массива. */

}

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

Операторы new и delete, как правило, перегружаются относительно класса. Ради простоты в следующем примере используется не новая схема распределения памяти, а перегруженные функции new и delete, которые просто вызывают С-ориентированные функции выделения памяти malloc() и free(). (В своем собственном приложении вы вольны реализовать любой метод выделения памяти.)

Чтобы перегрузить операторы new и delete для конкретного класса, достаточно сделать эти перегруженные операторные функции членами этого класса. В следующем примере программы операторы new и delete перегружаются для класса three_d. Эта перегрузка позволяет выделить память для объектов и массивов объектов, а затем освободить ее.

// Демонстрация перегруженных операторов new и delete.

#include <iostream>

#include <new>

#include <cstdlib>

using namespace std;

class three_d {

int x, y, z; // 3-мерные координаты

public:

three_d() {

x = у = z = 0;

cout << «Создание объекта 0, 0, 0\n»;

}

three_d(int i, int j, int k) {

x = i;

у = j;

z = k;

cout << «Создание объекта » << i << «, «;

cout << j << «, » << k;

cout << ‘\n’;

}

~three_d() { cout << «Разрушение объекта\n»; }

void *operator new(size_t size);

void *operator new[](size_t size);

void operator delete(void *p);

void operator delete[](void *p);

void show();

};

// Перегрузка оператора new для класса three_d.

void *three_d::operator new(size_t size)

{

void *p;

cout <<«Выделение памяти для объекта класса three_d.\n»;

р = malloc(size);

// Генерирование исключения в случае неудачного выделения памяти.

if(!р) {

bad_alloc ba;

throw ba;

}

return р;

}

// Перегрузка оператора new для массива объектов типа three_d.

void *three_d::operator new[](size_t size)

{

void *p;

cout <<«Выделение памяти для массива three_d-oбъeктoв.»;

cout << «\n»;

// Генерирование исключения при неудаче.

р = malloc(size);

if(!р) {

bad_alloc ba;

throw ba;

}

return p;

}

// Перегрузка оператора delete для класса three_d.

void three_d::operator delete(void *p)

{

cout << «Удаление объекта класса three_d.\n»;

free(p);

}

// Перегрузка оператора delete для массива объектов типа three_d.

void three_d::operator delete[](void *p)

{

cout << «Удаление массива объектов типа three_d.\n»;

free(р);

}

// Отображение координат X, Y, Z.

void three_d::show()

{

cout << x << «, «;

cout << у << «, «;

cout << z << «\n»;

}

int main()

{

three_d *p1, *p2;

try {

p1 = new three_d[3]; // выделение памяти для массива

р2 = new three_d(5, 6, 7); // выделение памяти для объекта

}

catch (bad_alloc ba) {

cout << «Ошибка при выделении памяти.\n»;

return 1;

}

p1[1].show();

p2->show();

delete [] p1; // удаление массива

delete р2; // удаление объекта

return 0;

}

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

Выделение памяти для массива three_d-oбъeктoв.

Создание объекта 0, 0, 0

Создание объекта 0, 0, 0

Создание объекта 0, 0, 0

Выделение памяти для объекта класса three_d.

Создание объекта 5, 6, 7

0, 0, 0

5, б, 7

Разрушение объекта

Разрушение объекта

Разрушение объекта

Удаление массива объектов типа three_d.

Разрушение объекта

Удаление объекта класса three_d.

Первые три сообщения Создание объекта 0, 0, 0 выданы конструктором класса three_d (который не имеет параметров) при выделении памяти для трехэлементного массива. Как упоминалось выше, при выделении памяти для массива автоматически вызывается конструктор каждого элемента. Сообщение Создание объекта 5, б, 7 выдано конструктором класса three_d (который принимает три аргумента) при выделении памяти для одного объекта. Первые три сообщения Разрушение объекта выданы деструктором в результате удаления трехэлементного массива, поскольку при этом автоматически вызывался деструктор каждого элемента массива. Последнее сообщение Разрушение объекта выдано при удалении одного объекта класса three_d. Важно понимать, что, если операторы new и delete перегружены для конкретного класса, то в результате их использования для данных других типов будут задействованы оригинальные версии операторов new и delete. Это

означает, что при добавлении в функцию main() следующей строки будет выполнена стандартная версия оператора new.

int *f = new int; // Используется стандартная версия оператора new.

И еще. Операторы new и delete можно перегружать глобально. Для этого достаточно объявить их операторные функции вне классов. В этом случае стандартные версии С++- операторов new и delete игнорируются вообще, и во всех запросах на выделение памяти используются их перегруженные версии. Безусловно, если вы при этом определите версию операторов new и delete для конкретного класса, то эти «классовые» версии будут применяться при выделении памяти (и ее освобождении) для объектов этого класса. Во всех же остальных случаях будут использоваться глобальные операторные функции.

Перегрузка nothrow-версии оператора new

Можно также создать перегруженные nothrow-версии операторов new и delete. Для этого используйте такие схемы.

// Перегрузка nothrow-версии оператора new.

void *operator new(size_t size, const nothrow_t &n)

{

// Выделение памяти.

if(success) return pointer_to_memory;

else return 0;

}

// Перегрузка nothrow-версии оператора new для массива.

void *operator new[](size_t size, const nothrow_t &n)

{

// Выделение памяти.

if(success) return pointer_to_memory;

else return 0;

}

// Перегрузка nothrow-версии оператора delete.

void operator delete(void *p, const nothrow_t &n)

{

// Освобождение памяти.

}

// Перегрузка nothrow-версии оператора delete для массива.

void operator delete[](void *p, const nothrow_t &n)

{

// Освобождение памяти.

}

Тип nothrow_t определяется в заголовке <new>. Параметр типа nothrow_t не используется. В качестве упражнения поэкспериментируйте с nothrow-версиями операторов new и delete самостоятельно.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]

  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #

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

Программная ошибка: что это и почему возникает

Программная ошибка — это дефект в коде. Из-за него программа сбоит или выдает неверные результаты. Некоторые ошибки серьезные — например, блокируют логин и пароль, из-за чего пользователь не может попасть в личный кабинет. А другие незаметны. Некоторое время программа работает как будто бы исправно — и только потом начинает глючить.

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

Научитесь находить ошибки в приложениях и на сайтах до того, как ими начнут пользоваться клиенты. Для этого освойте профессию «Инженер по тестированию». Изучать язык программирования необязательно. Тестировщик работает с готовыми сайтами, приложениями, сервисами, а не с кодом. В программе от Skypro: четыре проекта для портфолио, практика с обратной связью, все основные инструменты тестировщика.

Ошибки часто называют багами, но подразумевают под ними разное, например:

Ворнинги, или предупреждения. Возникают, когда программа начинает вести себя не так, как задумывалось. Не являются критичными ошибками. Программа с ворнингами работает, но с аномалиями.

Исключения. Это не ошибки, а особые ситуации, которые нужно обработать.

Синтаксические ошибки. Это ошибка в программе, связанная с написанием кода. Пример: программист забыл поставить точку или неверно написал название оператора. Если не исправить, код программы не запустится, а останется просто текстом.

Классификация багов

У багов есть два атрибута — серьезности (Severity) и приоритета (Priority). Серьезность касается технической стороны, а приоритет — организационной.

🚨 По серьезности. Атрибут показывает, как сильно ошибка влияет на общую функциональность программы. Чем выше значение атрибута, тем хуже.

По серьезности баги классифицируют так:

  • Blocker — блокирующий баг. Программа запускается, но спустя время баг останавливает ее выполнение. Чтобы снова пользоваться программой, блокирующую ошибку в коде устраняют.
  • Critical — критический баг. Нарушает функциональность программы. Появляется в разных частях кода, из-за этого основные функции не выполняются.
  • Major — существенный баг. Не нарушает, но затрудняет работу основного функционала программы либо не дает функциям выполняться так, как задумано.
  • Minor — незначительный баг. Слабо влияет на функционал программы, но может нарушать работу некоторых дополнительных функций.
  • Trivial — тривиальный баг. На работу программы не влияет, но ухудшает общее впечатление. Например, на экране появляются посторонние символы или всё рябит.

🚦 По приоритету. Атрибут показывает, как быстро баг необходимо исправить, пока он не нанес программе приличный ущерб. Бывает таким:

  • Top — наивысший. Такой баг — суперсерьезный, потому что может обвалить всю программу. Его устраняют в первую очередь.
  • High — высокий. Может затруднить работу программы или ее функций, устраняют как можно скорее.
  • Normal — обычный. Баг программу не ломает, просто где-то что-то будет работать не совсем верно. Устраняют в штатном порядке.
  • Low — низкий. Баг не влияет на программу. Исправляют, только если у команды есть на это время.

Типы ошибок в программе

🧨 Логические. Приводят к тому, что программа зависает, работает не так, как надо, или выдает неожиданные результаты — например, не записывает файл, а стирает.
Логические ошибки коварны: их трудно обнаружить. Программа выглядит так, будто в ней всё правильно, но при этом работает некорректно. Чтобы победить логические ошибки, специалист должен хорошо ориентироваться в коде программы.

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

🧨 Взаимодействия. Это ошибка в участке кода, который отвечает за взаимодействие с аппаратным или программным окружением. Такая ошибка возникает, например, если неправильно использовать веб-протоколы. Исправляется элементарно: разработчик переписывает нужный кусок кода.

🧨 Компиляционные. Любая программа — это текст. Чтобы он заработал как программа, используют компилятор. Он преобразует программный код в машинный, но одновременно может вызывать ошибки.

Компиляционные баги появляются, если что-то не так с компилятором или в коде есть синтаксические ошибки. Компилятор будто ругается: «Не понимаю, что тут написано. Не знаю, как обработать».

🧨 Ошибки среды выполнения. Возникают, когда программа скомпилирована и уже выглядит как файл — жми и работай. Юзер запускает файл, а программа тормозит и виснет. Причина — нехватка ресурсов, например памяти или буфера.

Такой баг — ошибка разработчика. Он не предвидел реальные условия развертывания программы. Теперь ему надо вернуться в исходный код и поправить фрагмент.

🧨 Арифметические. Бывает, в коде есть числовые переменные и математические формулы. Если где-то проблема — не указаны константы или округление сработало не так, возникает баг. Надо лезть в код и проверять математику.

Инженер-тестировщик: новая работа через 9 месяцев

Получится, даже если у вас нет опыта в IT

Получить
программу

Что такое исключения в программах

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

Как это происходит:

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

Исключения бывают программными и аппаратными:

  • Аппаратные создает процессор. К ним относят деление на ноль, выход за границы массива, обращение к невыделенной памяти.
  • Программные создает операционка и приложения. Возникают, когда программа их инициирует: аномальная ситуация возникла — программа создала исключение.

Как контролировать баги в программе

🔧 Следите за компилятором. Когда компилятор преобразует текст программы в машинный код, то подсвечивает в нём сомнительные участки, которые способны вызывать баги. Некоторые предупреждения не обозначают баг как таковой, а только говорят: «Тут что-то подозрительное». Всё подозрительное надо изучать и прорабатывать, чтобы не было проблемы в будущем.

🔧 Используйте отладчик. Это программа, которая без участия айтишника проверяет, исправно ли работает алгоритм. В случае чего сообщает об ошибках. Например, отладчик используют для построчного выполнения программы. Вместе с тем проверяют значения переменных: фактические сравнивают с ожидаемыми. Если что-то не сходится, ищут баги и исправляют.

🔧 Проводите юнит-тесты. Это когда разработчик или тестировщик описывает ситуации для каждого компонента и указывает, к какому результату должна привести программа. Потом запускает проверку. Если результат не совпадает с ожидаемым, появляется предупреждение. Дальше программисты находят и устраняют проблему.

Ключевое: что такое ошибки в программировании

  • Ошибка в программировании — это дефект кода, баг, который может вызывать в программе сбои и неожиданное поведение.
  • По серьезности баги делятся на блокирующие, критические, существенные, незначительные, тривиальные. По приоритету — на наивысший, высокий, обычный, низкий.
  • Ошибки в коде могут быть разными, например связанные с логикой программы. Или с математическими вычислениями — логические. Еще бывают синтаксические, ошибки взаимодействия, компиляционные и ошибки среды выполнения.
  • Некоторые ошибки помогают ловить обработчики исключений.
  • Чтобы находить ошибки в коде, тестировщики используют компиляторы, отладчики и пишут юнит-тесты.


1


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


2


Принцип обработки исключений Функция, которая не может выполнить требуемое действие, генерирует исключение (throw). Исключение обрабатывается где-то в контролируемом блоке (try), из которого вызывается эта функция. При этом отыскивается обработчик исключения (catch) и ему передается управление. Если обработчик не найден, то вызывается функция terminate, аварийно завершающая программу.


3


Контролируемый блок Контролируемый блок – код, внутри которого может произойти исключение. При этом контролируются все вложенные вызовы функций. try { // операторы } catch(тип1 имя1) { //обработка исключения 1 } … catch(тип_n имя_n) { //обработка исключения n }


4


Обработчик исключений Обработчик начинается со слова catch, за которым следует в скобках тип исключения. Обработчики должны располагаться непосредственно после блока try. Если указать вместо параметра многоточие (…), то обработчик будет обрабатывать исключения любых типов. После обработки исключения управление передается первому оператору, следующему за всеми обработчиками.


5


Порождение исключения throw выражение; Тип выражения определяет тип исключения. Обычно используются специальные классы исключений. throw без параметров используется внутри обработчика исключения для передачи исключения на более высокий уровень обработки. При поиске обработчика исключений автоматически вызываются деструкторы локальных объектов.


6


Пример обработки исключений #include template class stack { private: Data st[Max]; int top; public: class StackError { }; stack (): top(0) {} void push(Data x) { if (top >= Max) throw StackError(); st[top++] = x; } Data pop() { if (top


7


Пример обработки исключений int main() { stack s1; try { s1.push(‘a’); s1.push(‘b’); s1.push(‘c’); cout


8


Использование разных классов исключений #include template class stack { private: Data st[Max]; int top; public: class Full { }; class Empty { }; stack (): top(0) {} void push(Data x) { if (top >= Max) throw Full(); st[top++] = x; } Data pop() { if (top


9


Использование разных классов исключений int main() { stack s1; try { s1.push(‘a’); s1.push(‘b’); // s1.push(‘c’); cout


10


Исключения с аргументами template class stack { private: Data st[Max]; int top; public: class Error { public: char sender[20], err[20]; Error( char *s, char *t) { strcpy(sender,s); strcpy(err,t); } }; void push(Data x) { if (top >= Max) throw Error(«Push»,»Full»); st[top++] = x; } Data pop() { if (top


11


Исключения с аргументами int main() { stack s1; try { s1.push(‘a’); s1.push(‘b’); s1.push(‘c’); cout


12


Встроенные исключения Стандартная библиотека C++ содержит несколько предопределенных классов исключений. Все они имеют общего предка exception. Примеры: bad_alloc – ошибка при распределении памяти; invalid_argument – непр. аргумент ф-ции и т.д.


13


Многофайловые программы При коллективной разработке больших программ неизбежно возникает необходимость использования нескольких исходных файлов. Обычно каждый файл содержит описание нескольких функций или классов. Рассмотрим создание библиотек классов.


14


Библиотека классов Библиотека состоит из интерфейса (объявления функций и классов) и реализации (тел функций и методов). Интерфейс представляет собой заголовочный файл с объявлениями с расширением.H. Реализация – откомпилированный объектный (OBJ) или библиотечный (LIB) файл.


15


Сборка многофайловых программ Заголовочный файл с интерфейсом включается в любой исходный файл, использующий классы, при помощи #include. Исходные и заголовочные файлы для компиляции включаются в файл проекта (BPR, DEV, DSP). В этом файле хранится информация о дате файлов, что позволяет перекомпилировать только измененные файлы.


16


Межфайловые переменные Переменная должна быть определена только в одном файле, в других – объявлена с помощью extern. Чтобы определить переменные с одинаковыми именами в разных файлах, используется static. //файл A int Var; static int X; //файл B extern int Var; static int X; Var = 10;


17


Межфайловые функции и классы. Функция определяется в одном файле, а объявляется во всех, которые ее используют. Чтобы определить функции с одинаковыми именами в разных файлах, используется static. Классы должны быть определены во всех файлах, где используются. Поэтому обычно классы определяются в заголовочных файлах и включаются во все файлы, где используются классы. Определения методов должны быть в единственном экземпляре в любых файлах, использующих заголовочный файл. Определения шаблонов функций и классов обязательно должны включаться в каждый файл, поэтому они размещаются в заголовочном файле.


18


Пример определения классов //stack.h class stack { private: char st[100]; int top; public: stack (): top(0) {} void push (char x); char pop(); bool empty(); };


19


Пример определения классов // stack.cpp #include stack.h void stack::push(char x) {st[top++] = x; } char stack:: pop() { return st[—top]; }


20


Ошибки повторного включения Определение функции или переменной не должно включаться в файл дважды. //headtwo.h int Var; //headone.h #include headtwo.h // app.cpp #include headone.h #include headtwo.h


21


Директивы препроцессора Для предупреждения повторного включения используются директивы препроцессора. #define имя // определение константы #if выражение //условная компиляция … #elif выражение … #else … #endif В выражениях можно проверить, определена ли константа с помощью defined(имя).


22


Пример предотвращения повторного включения //stack.h #if !defined (STACK) #define STACK class stack { private: char st[100]; int top; public: stack (): top(0) {} void push (char x); char pop(); bool empty(); }; #endif


23


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


24


Описание пространства имен namespace имя { … } Пример: namespace math { const double pi = ; double len (double r) { return 2 * pi * r; } }


25


Доступ к членам пространства имен Доступ осуществляется через область видимости cout


26


Неоднократное определение пространства имен Определения пространства имен могут встречаться в программе неоднократно. namespace math { const double pi = ;} … namespace math { double len (double r) { return 2 * pi * r; } } В этом случае второе определение является продолжением первого. В стандартной библиотеке C++ большинство классов описаны в пространстве имен std. Можно описать неименованное пространство имен. Оно получит имя, совпадающее с именем файла.

Понятие исключительной ситуации. Блок try…catch. Оператор throw. Примеры использования


Содержание

  • 1. Типы ошибок, которые могут возникать в программах
  • 2. Понятие исключительной ситуации
  • 3. Понятие исключения
  • 4. Средства языка C++ для обработки исключительных ситуаций. Общая форма конструкции try…catch. Назначение
  • 5. Оператор throw. Назначение
  • 6. Примеры использования блока try…catch
  • 7. Пример использования блока try…catch в функции
  • 8. Пример программы, которая генерирует исключение в одной функции, а перехватывает его в другой функции
  • 9. Использование блока catch(…). Перехват всех возможных исключительных ситуаций. Пример
  • Связанные темы

Поиск на других ресурсах:

1. Типы ошибок, которые могут возникать в программах

В программах на C++ могут возникать ошибки. Различают три типа ошибок, которые могут возникать в программах:

  • синтаксические. Это ошибки в синтаксисе языка C++. Они могут встречаться в именах операторов, функций, разделителей и т.д. В этом случае компилятор определяет наличие синтаксической ошибки и выдает соответствующее сообщение. В результате исполняющий (*.exe) файл не создается и программа не выполняется;
  • логические. Это ошибки программиста, которые сложно обнаружить на этапе разработки программы. Эти ошибки обнаруживаются на этапе выполнения во время тестирования работы программы. Логические ошибки можно обнаружить только по результатам работы программы. Примером логических ошибок может быть неправильная работа с указателями в случаях выделения/освобождения памяти;
  • ошибки времени выполнения. Такие ошибки возникают во время работы программы. Ошибки времени выполнения могут быть логическими ошибками программиста, ошибками внешних событий (например, нехватка оперативной памяти), неверным вводом данных пользователем и т.п. В результате возникновения ошибки времени выполнения, программа приостанавливает свою работу. Поэтому, важно перехватить эту ошибку и правильно обработать ее для того, чтобы программа продолжила свою работу без остановки.

Данная тема освещает применение механизма перехвата ошибок времени выполнения.

 

2. Понятие исключительной ситуации

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

Примеры действий в программе, которые могут привести к возникновению исключительных ситуаций:

  • деление на нуль;
  • нехватка оперативной памяти при использовании оператора new для ее выделения (или другой функции);
  • доступ к элементу массива за его пределами (ошибочный индекс);
  • переполнение значения для некоторого типа;
  • взятие корня из отрицательного числа;
  • другие ситуации.

 

3. Понятие исключения

В языке C++ исключение – это специальный объект класса или значение базового типа, который описывает (определяет) конкретную исключительную ситуацию и соответствующим образом обрабатывается.

При написании программы система описания исключительных ситуаций выбирается программистом по собственному усмотрению. Можно создать свою квалификацию ошибок, которые могут возникать в программе. Например, программист может квалифицировать разные типы ошибок числовым (целочисленным) значением или разработать собственную иерархию классов описывающих исключительные ситуации. Кроме того, можно использовать возможности классов C++, которые являются производными от класса exception.

 

4. Средства языка C++ для обработки исключительных ситуаций. Общая форма конструкции try…catch. Назначение

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

Механизм перехвата исключений C++ позволяет генерировать исключения в том месте, в котором оно возникает – это очень удобно. Не нужно «выдумывать» собственные способы обработки исключений, которые возникают в функциях нижних уровней, для того чтобы передать их в функции высших уровней.

Для перехвата и обработки исключительных ситуаций в языке C++ введена конструкция try…catch, которая имеет следующую общую форму:

try {
  // тело блока try
  // ...
  // генерирование исключения оператором throw
}
catch(type1 argument1)
{
  // тело блока catch
}
catch(type2 argument2)
{
  // тело блока catch
}
...
catch(typeN argumentN)
{
  // тело блока catch
}

где

  • type1, type2, …, typeN – соответственно тип аргументов argument1, argument2, …, argumentN.

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

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

Если в блоке try возникнет исключительная ситуация, которая не предусмотрена блоком catch, то вызывается стандартная функция terminate(), которая по умолчанию вызовет функцию abort(). Эта стандартная функция останавливает выполнение программы.

 

5. Оператор throw. Назначение

Чтобы в блоке try сгенерировать исключительную ситуацию, нужно использовать оператор throw. Оператор throw может быть вызван внутри блока try или внутри функции, которая вызывается из блока try.

Общая форма оператора throw следующая

throw исключение;

В результате выполнения оператора throw генерируется исключение некоторого типа. Это исключение должно быть обработано в блоке catch.

 

6. Примеры использования блока try…catch

Пример 1. Демонстрируется использование блока try…catch для обработки выражения:

В данном выражении в трех случаях может возникнуть исключительная ситуация:

  • корень из отрицательного числа a, если a<0;
  • корень из отрицательного числа b, если b<0;
  • деление на 0, если b=0.

Поэтому, в блоке try…catch нужно обработать эти три случая.

Текст программы типа Console Application следующий:

#include <iostream>
using namespace std;

void main()
{
  // обработка выражения sqrt(a)/sqrt(b)
  double a, b;
  cout << "a = ";
  cin >> a;

  cout << "b = ";
  cin >> b;

  double c;

  try { // начало блока try
    if (b == 0)
      throw 1;
    if (b < 0)
      throw 2;
    if (a < 0)
      throw 2;

    c = sqrt(a) / sqrt(b);
    cout << "c = " << c << endl;
  }
  catch (int e) // перехват ошибки
  {
    if (e == 1)
      cout << "Division by 0." << endl;
    if (e == 2)
      cout << "Negative root." << endl;
  }
}

Результат работы программы

a = 5
b = 0
Division by 0.

После применения блока try..catch работа программы не прекращается.

Пример 2. Другой вариант обработки выражения из примера 1. Здесь блок try…catch содержит два оператора catch.

#include <iostream>
using namespace std;

void main()
{
  // обработка выражения sqrt(a)/sqrt(b) - вариант 2
  double a, b;
  cout << "a = ";
  cin >> a;

  cout << "b = ";
  cin >> b;

  double c;
  string s;

  try { // начало блока try
    if (b == 0)
      throw "Division by 0.";
    if (b < 0)
      throw "Negative root.";
    if (a < 0)
      throw "Negative root.";

    // если исключительных ситуаций нет, то продолжить вычисления
    c = sqrt(a) / sqrt(b);
    cout << "c = " << c << endl;
  }
  catch (int e) // перехват ошибки типа int
  {
    if (e == 1)
      cout << "Division by 0." << endl;
    if (e == 2)
      cout << "Negative root." << endl;
  }
  catch (const char* e) // перехват ошибки типа const char*
  {
    cout << e << endl;
  }
}

 



7. Пример использования блока try…catch в функции

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

Текст программы для приложения типа Console Application следующий

#include <iostream>
using namespace std; 

// Функция вычисления значения по заданной строке символов
int StrToInt(const char* str)
{
  char s[20];
  int t, i;
  long res = 0; // результат возврата из функции
  int len = strlen(str);

  try
  {
    t = 1;
    if (str[0] == '-') 
      t = -1; // проверка, первый ли символ '-'

    // цикл конвертирования строки в число типа int
    i = len - 1;
    while (i >= 0)
    {
      if (str[i] == '-')
      {
        if (i == 0) break; // если перший символ '-', то все в порядке
        else throw "Bad position of minus.";
      }

      // если в строке недопустимые символы, то сгенерировать исключение
      if (str[i] < '0') throw "Bad symbols";
      if (str[i] > '9') throw "Bad symbols";

      res = res + (str[i] - '0')*t;

      t *= 10;
      i--;
    }

    // если результат выходит за пределы диапазона для 32-разрядных
    // целочисленных значений, то сгенерировать соответствующее исключения
    if (res > INT32_MAX)
      throw "Out of range.";
    if (res < INT32_MIN)
      throw "Out of range.";
    return res;
  }
  catch (const char* e)
  {
    cout << e << endl;
    return 0;
  }
}

void main()
{
  int d;
  d = StrToInt("125");
  cout << "d = " << d;
}

Вышеприведенная программа может быть переписана так, что блок try…catch размещается в функции main(), как показано ниже

#include <iostream>
using namespace std; 

// Функция вычисления значения по заданной строке символов
int StrToInt2(const char* str)
{
  char s[20];
  int t, i;
  long res = 0; // результат работы функции
  int len = strlen(str);

  t = 1;
  if (str[0] == '-') t = -1;

  i = len - 1;
  while (i >= 0)
  {
    if (str[i] == '-')
    {
      if (i == 0) break; // если первый символ '-', то все в порядке
      else throw "Bad position of minus."; // иначе, сгенерировать исключение
    }

    if (str[i] < '0') throw "Bad symbols";
    if (str[i] > '9') throw "Bad symbols";

    res = res + (str[i] - '0')*t;
    t *= 10;
    i--;
  }

  // если результат выходит за пределы диапазона для 32-разрядных
  // целочисленных значений, то сгенерировать соответствующее исключения
  if (res > INT32_MAX)
    throw "Out of range.";
  if (res < INT32_MIN)
    throw "Out of range.";
  return res;
}

void main()
{
  int d;

  // блок try...catch размещается в функции main() высшего уровня,
  // а исключение генерируется в функции StrToInt2() нижнего уровня
  try {
    d = StrToInt2("19125");
    cout << "d = " << d;
  }
  catch (const char* e)
  {
    cout << e << endl;
  }
}

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

Результат выполнения программы

d = 19125

Если вызов функции StrToInt2() перенести за пределы оператора try

void main()
{
  int d;

  try {
    //d = StrToInt2("19125");
    //cout << "d = " << d;
  }
  catch (const char* e)
  {
    cout << e << endl;
  }

  // вызов функции за пределами оператора try
  d = StrToInt2("в19125");
}

то исключительные ситуации в функции StrToInt2() обрабатываться не будут. При возникновении исключительной ситуации в функции StrToInt2() компилятор сгенерирует собственную ошибку

Exception Unhandled

что означает, что исключение необработано.

 

8. Пример программы, которая генерирует исключение в одной функции, а перехватывает его в другой функции

В примере, в функции нижнего уровня GenerateException() генерируется исключение типа const char*. Функция проверяет допустимые границы входного параметра index.

В функции верхнего уровня ProcessException() происходит вызов функции GenerateException(). Этот вызов взят в блок try.

Текст программы следующий:

#include <iostream>
using namespace std;

// Пример Программы, которая генерирует исключение в одной функции, а перехватывает его в другой
// Функция генерирует исключение "Out of index",
// если значение index находится за пределами диапазона 0..9
void GenerateException(int index)
{
  if ((index < 0) || (index > 9))
    throw "Out of index";
}

// Функция, которая перехватывает исключение "Out of index"
void ProcessException()
{
  int index;
  cout << "index = ";
  cin >> index;

  // 1. Вызвать исключительную ситуацию без обработки,
  // компилятор выдаст сообщение "Exception unhandled"
  // GenerateException(-3);

  // 2. Вызвать исключительную ситуацию с обработкой блоком try...catch
  try {
    GenerateException(index); // вызов функции, которая генерирует исключение
    cout << "OK!" << endl; // если index в пределах 0..9, то OK!
  }
  catch (const char* e)
  {
    cout << "Error: " << e << endl;
  }
}

void main()
{
  ProcessException();
}

Результат выполнения программы

index = -5
Error: Out of index

 

9. Использование блока catch(…). Перехват всех возможных исключительных ситуаций. Пример

Бывают случаи, когда нужно перехватить все исключительные ситуации подряд. Для этого, в C++ используется блок catch(…), который имеет следующую общую форму

catch(...)
{
  // Обработка всех исключительных ситуаций
  // ...
}

Пример. В примере демонстрируется использование блока catch(…) для обработки ситуаций любого типа.

В программе реализованы:

  • функция DivNumbers(), которая возвращает результат деления двух чисел, введенных из клавиатуры. В функции генерируется исключение типа int, если значение делителя равно 0;
  • функция SqRoot(), возвращающая корень из отрицательного числа. В функции генерируется исключение типа const char*, если значение параметра number отрицательно;
  • функция ProcessException(). Эта функция демонстрирует работу функций DivNumbers() и SqRoot(). В функции используется инструкция try…catch().
#include <iostream> 
using namespace std; 

// Пример. Демонстрация использования блока catch
// Функция, которая делит 2 числа и возвращает результат
double DivNumbers(double a, double b)
{
  if (b == 0) throw 1;
    return a / b;
}

// Функция, возвращающая корень из отрицательного числа
double SqRoot(double number)
{
  if (number < 0) throw "Negative number";
    return sqrt(number);
}

// Демонстрация блока catch(...)
void ProcessException()
{
  double v;

  // цикл отображения и вызова нужной функции
  while (1)
  {
    cout << "Input a function to call (1-2, 3-exit): " << endl;
    cout << "1-DivNumbers. 2-SqRoot" << endl;
    cout << ">>";
    cin >> v;

    // Вызвать различные варианты функций
    try {
      if (v == 1) // функция DivNumbers
      {
        double a, b;
        cout << "DivNumbers(double a, double b)" << endl;

        // ввести a, b
        cout << "a = "; cin >> a;
        cout << "b = "; cin >> b;

        // Вызвать функцию DivNumbers()
        double c = DivNumbers(a, b);
        cout << "c = " << c << endl;
      }
      if (v == 2)
      {
        double x, num;
        cout << "SqRoot(double num)" << endl;
        cout << "num = "; cin >> num;

        // Вызвать функцию SqRoot()
        x = SqRoot(num);
        cout << "x = " << x << endl;
      }
      if (v == 3) break;
    }
    catch (const char* e)
    {
      cout << "Error. Text = " << e << endl;
    }
    catch (...) // все другие типы исключений
    {
      cout << "Error in block catch(...)." << endl;
    }
  }
}

void main()
{
  ProcessException();
}

Как видно из текста функции ProcessException() вызов функций DivNumbers() и SqRoot() взят в блок try…catch

// Вызвать разные варианты функций
try {
  ...
}
catch (const char* e)
{
  cout << "Error. Text = " << e << endl;
}
catch (...) // все другие типы исключений
{
  cout << "Error in block catch(...)." << endl;
}

В блоке try…catch обрабатываются

  • исключение типа const char*;
  • все другие типы исключений. В этом случае используется инструкция catch(…).

Результат работы программы

Input a function to call (1-2, 3-exit):
1-DivNumbers. 2-SqRoot
>>2
SqRoot(double num)
num = -4
Error. Text = Negative number
Input a function to call (1-2, 3-exit):
1-DivNumbers. 2-SqRoot
>>1
DivNumbers(double a, double b)
a = 3
b = 0
Error in block catch(...).
Input a function to call (1-2, 3-exit):
1-DivNumbers. 2-SqRoot
>>1
DivNumbers(double a, double b)
a = 2
b = 5
c = 0.4
Input a function to call (1-2, 3-exit):
1-DivNumbers. 2-SqRoot
>>3

 


Связанные темы

  • Пример создания иерархии классов для обработки исключительных ситуаций

 


Ошибки в программировании – дело обычное, хоть и неприятное. В данной статье будет рассказано о том, какими бывают ошибки (баги), а также что собой представляют исключения.

Определение

Ошибка в программировании (или так называемый баг) – это ситуация у разработчиков, при которой определенный код вследствие обработки выдает неверный результат. Причин данному явлению множество: неисправность компилятора, сбои интерфейса, неточности и нарушения в программном коде.

Баги обнаруживаются чаще всего в момент отладки или бета-тестирования. Реже – после итогового релиза готовой программы. Вот несколько вариантов багов:

  1. Появляется сообщение об ошибке, но приложение продолжает функционировать.
  2. ПО вылетает или зависает. Никаких предупреждений или предпосылок этому не было. Процедура осуществляется неожиданно для пользователя. Возможен вариант, при котором контент перезапускается самостоятельно и непредсказуемо.
  3. Одно из событий, описанных ранее, сопровождается отправкой отчетов разработчикам.

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

История происхождения термина

Баг – слово, которое используется разработчиками в качестве сленга. Оно произошло от слова «bug» – «жук». Точно неизвестно, откуда в программировании и IT возник соответствующий термин. Существуют две теории:

  1. 9 сентября 1945 года ученые из Гарварда тестировали очередную вычислительную машину. Она называлась Mark II Aiken Relay Calculator. Устройство начало работать с ошибками. Когда его разобрали, то ученые заметили мотылька, застрявшего между реле. Тогда некая Грейс Хоппер назвала произошедший сбой упомянутым термином.
  2. Слово «баг» появилось задолго до появления Mark II. Термин использовался Томасом Эдисоном и указывал на мелкие недочеты и трудности. Во время Второй Мировой войны «bugs» называли проблемы с радарной электроникой.

Второй вариант кажется более реалистичным. Это факт, который подтвержден документально. Со временем научились различать различные типы багов в IT. Далее они будут рассмотрены более подробно.

Как классифицируют

Ошибки работы программ разделяются по разным факторам. Классификация у рядовых пользователей и разработчиков различается. То, что для первых – «просто программа вылетела» или «глючит», для вторых – огромная головная боль. Но существует и общепринятая классификация ошибок. Пример – по критичности:

  1. Серьезные неполадки. Это нарушения работоспособности приложения, которые могут приводить к непредвиденным крупным изменениям.
  2. Незначительные ошибки в программах. Чаще всего не оказывают серьезного воздействия на функциональность ПО.
  3. Showstopper. Критические проблемы в приложении или аппаратном обеспечении. Приводят к выходу программы из строя почти всегда. Для примера можно взять любое клиент-серверное приложение, в котором не получается авторизоваться через логин и пароль.

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

Также существуют различные виды сбоев в плане частоты проявления: постоянные и «разовые». Вторые встречаются редко, чаще – при определенных настройках и действиях со стороны пользователя. Первые появляются независимо от используемой платформы и выполненных клиентом манипуляций.

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

Виды

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

Разработчики выделяют следующие типы ошибок по уровню сложности:

  1. «Борбаг» – «стабильная» неполадка. Она легко обнаруживается на этапе разработки и компилирования. Иногда – во время тестирования наработкой исходной программы.
  2. «Гейзенбаг» – баги с поддержкой изменения свойств, включая зависимость от среды, в которой было запущено приложение. Сюда относят периодические неполадки в программах. Они могут исчезать на некоторое время, но через какой-то промежуток вновь дают о себе знать.
  3. «Мандельбаг» – непредвиденные ошибки. Обладают энтропийным поведением. Предсказать, к чему они приведут, практически невозможно.
  4. «Шрединбаг» – критические неполадки. Приводят к тому, что злоумышленники могут взломать программу. Данный тип ошибок обнаружить достаточно трудно, потому что они никак себя не проявляют.

Также есть классификация «по критичности». Тут всего два варианта – warning («варнинги») и критические весомые сбои. Первые сопровождаются характерными сообщениями и отчетами для разработчиков. Они не представляют серьезной опасности для работоспособности приложения. При компилировании такие сбои легко исправляются. В отдельных случаях компилятор справляется с этой задачей самостоятельно. А вот критические весомые сбои говорят сами за себя. Они приводят к серьезным нарушениям ПО. Исправляются обычно путем проработки логики и значительных изменений программного кода.

Типы багов

Ошибки в программах бывают:

  • логическими;
  • синтаксическими;
  • взаимодействия;
  • компиляционные;
  • ресурсные;
  • арифметические;
  • среды выполнения.

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

Ошибки синтаксиса

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

Синтаксические ошибки – ошибки синтаксиса, правил языка. Вот пример в Паскале:

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

Логические

Тут стоит выделить обычные и арифметические типы. Вторые возникают, когда программе при работе необходимо вычислить много переменных, но на каком-то этапе расчетов возникают неполадки или нечто непредвиденное. Пример – получение в результатах «бесконечности».

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

Выше – пример логической ошибки в программе. Тут:

  1. Происходит сравнение значения i с 15.
  2. На экран выводится сообщение, если I = 15.
  3. В заданном цикле i не будет равно 15. Связано это с диапазоном значений – от 1 до 10.

Может показаться, что ошибка безобидная. В приведенном примере так и есть, но в более крупных программах такое явление приводит к серьезным последствиям.

Время выполнения

Run-time сбои – это ошибка времени выполнения программы. Встречается даже когда исходный код лишен логических и синтаксических ошибок. Связаны такие неполадки с ходом выполнения программного продукта. Пример – в процессе функционирования ПО был удален файл, считываемый программой. Если игнорировать подобные неполадки, можно столкнуться с аварийным завершением работы контента.

Самый распространенный пример в данной категории – это неожиданное деление на ноль. Предложенный фрагмент кода с точки зрения синтаксиса и логики написан грамотно. Но, если клиент наберет 0, произойдет сбой системы.

Компиляционный тип

Встречается при разработке на языках высокого уровня. Во время преобразований в машинный тип «что-то идет не так». Причиной служат синтаксические ошибки или сбои непосредственно в компиляторе.

Наличие подобных неполадок делает бета-тестирование невозможным. Компиляционные ошибки устраняются при разработке-отладке.

Ресурсные

Ресурсный тип ошибок – это сбои вроде «переполнение буфера» или «нехватка памяти». Тесно связаны с «железом» устройства. Могут быть вызваны действиями пользователя. Пример – запуск «свежих» игр на стареньких компьютерах.

Исправить ситуацию помогают основательные работы над исходным кодом. А именно – полное переписывание программы или «проблемного» фрагмента.

Взаимодействие

Подразумевается взаимодействие с аппаратным или программным окружением. Пример – ошибка при использовании веб-протоколов. Это приведет к тому, что облачный сервис не будет нормально функционировать. При постоянном возникновении соответствующей неполадки остается один путь – полностью переписывать «проблемный» участок кода, ответственный за соответствующий баг.

Исключения и как избежать багов

Исключение – событие, при возникновении которых начинается «неправильное» поведение программы. Механизм, необходимый для стабилизации обработки неполадок независимо от типа ПО, платформ и иных условий. Помогают разрабатывать единые концепции ответа на баги со стороны операционной системы или контента.

Исключения бывают:

  1. Программными. Они генерируются приложением или ОС.
  2. Аппаратными. Создаются процессором. Пример – обращение к невыделенной памяти.

Исключения нужны для охвата критических багов. Избежать неполадок помогут отладчики на этапе разработки. А еще – своевременное поэтапное тестирование программы.

P. S. Большой выбор курсов по тестированию есть и в Otus. Присутствуют варианты как для продвинутых, так и для начинающих пользователей.

Понравилась статья? Поделить с друзьями:

Интересное по теме:

  • Скайп ошибка передачи данных
  • Скайрим ошибка при запуске 0xc000007b
  • Скад ошибка при разложении матрицы номер узла
  • Скайп ошибка заблокировано
  • Скайрим ошибка при запуске 0xc00000142

  • Добавить комментарий

    ;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: