Мы завершаем путь. Всё это время мы стремились не допускать ошибок в выражениях, операторах, объявлениях, определениях, макроопределениях, программах. Но до сих пор у нас нет чёткого представления о том, что такое ошибка.
В общем случае под ошибкой мы будем понимать несоответствие правилу, алгоритму. Это рабочее определение. Конечно, правила бывают нечёткими, алгоритмы - некорректными. Это неважно. В любом случае можно сказать, что "всё не так, как должно быть". И этого достаточно.
Разные ошибки проявляют себя по-разному и могут быть обнаружены в разное время, на разных стадиях жизненного цикла программы и при различных обстоятельствах.
Выявлением некорректных макроопределений, несуществующих заголовочных файлов и неверных условий компиляции занимается препроцессор. Ошибки препроцессора выявляются на ранних этапах трансляции. Сами по себе они не проявляются.
Транслятору C++ хорошо известен синтаксис языка. Поэтому нарушение правил порождения слов, выражений и предложений, в том числе и неуместное использование ключевых слов, достаточно легко обнаруживается на стадии трансляции.
Транслятор распознаёт константные выражения различной сложности. Он способен самостоятельно производить арифметические вычисления. Так что с вопросами определения статических массивов также не возникает никаких проблем.
На этапе трансляции распознаются леводопустимые выражения. Поэтому значительная часть ошибок, связанных с некорректным использованием операций присвоения также выявляется на стадии трансляции.
Многие ошибки несоответствия типов также могут быть выявлены на этапе трансляции, в ходе создания объектного кода. Здесь следует вспомнить об операции явного преобразования типа, которая отключает контроль транслятора за типами.
На этапе создания исполнительного модуля программа (или система) компоновки способна распознать объявленные и неопределённые переменные и функции, а также незавершённые объявления классов.
В ряде случаев на этапе создания объектного кода могут выявляться неопределённые и неиспользуемые переменные, и даже потенциально опасные фрагменты кода. Транслятор может предупредить об использовании в выражениях неинициализированных переменных.
Однако не все расхождения с правилами, идеальным схемами и алгоритмам могут быть обнаружены до того момента, пока программа находится в состоянии разработки.
Существует категория ошибок, которые не способны выявить даже самые изощрённые препроцессоры, трансляторы и программы сборки. К их числу относятся так называемые ошибки времени выполнения. Эти ошибки проявляются в ходе выполнения программы.
Мы не раз подчёркивали, что в C++ часто возникают ситуации, при которых ответственность за правильность выполнения операций, операторов и даже отдельных функций целиком возлагается на программиста. Арифметические вычисления (деление на нуль), преобразования типа, работа с индексами и адресами, корректная формулировка условий в операторах управления, работа с потоками ввода-вывода - это далеко не полный перечень неконтролируемых в C++ ситуаций.
Ошибки времени выполнения, возникающие непосредственно в ходе выполнения программы, в терминах объектно-ориентированного программирования называются исключительными ситуациями. Исключительные ситуации - это события, которые прерывают нормальный ход выполнения программы.
Различают синхронные и асинхронные исключительные ситуации.
Синхронная исключительная ситуация возникает непосредственно в ходе выполнения программы, причём её причина заключается непосредственно в действиях, выполняемых самой программой.
Асинхронные исключительные ситуации непосредственно не связаны с выполнением программы. Их причинами могут служить аппаратно возбуждаемые прерывания (например, сигналы от таймера), сообщения, поступающие от внешних устройств или даже от локальной сети.
Реакция на исключительную ситуацию называется исключением.
Заметим, что исключительная ситуация не всегда неожиданна. Очень часто при разработке алгоритма уже закладывается определённая реакция на вероятную ошибку.
Например, функция, размещающая целочисленные значения в массиве по определённому индексу, может самостоятельно следить за допустимыми значениями индекса. Она возвращает единицу в случае успешного размещения значения и нуль, если значение параметра, определяющего индекс, не позволяет этого сделать.