Урок №108. Обработка ошибок, cerr и exit()

  Юрий  | 

    | 

  Обновл. 9 Июн 2019  | 

 6581

 ǀ   1 

При написании программ возникновение ошибок почти неизбежно. Ошибки делятся на две категории: синтаксические и семантические.

Синтаксические ошибки

Синтаксическая ошибка возникает при нарушении правил грамматики языка C++. Например:

если 7 не равно 8, то пишем "not equal";

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

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

Семантические ошибки


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

Возможно, программист хотел, чтобы вывелось 0 1 2 3, но, на самом деле, выведется 0 1 2 3 4.

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

Они могут возникать несколькими способами. Одной из наиболее распространённых семантических ошибок является логическая ошибка. Логическая ошибка возникает, когда программист неправильно программирует логику выполнения кода. Например, фрагмент кода выше имеет логическую ошибку. Вот ещё один пример:

Что произойдёт, если x будет равен 4? Условие выполнится как true, а программа выведет x is greater than 4. Логические ошибки иногда бывает довольно-таки трудно обнаружить.

Другой распространённой семантической ошибкой является ложное предположение. Ложное предположение возникает, когда программист предполагает, что что-то будет истинным или ложным, а оказывается наоборот. Например:

Заметили потенциальную проблему здесь? Предполагается, что пользователь введёт значение между 0 и длиной строки Hello, world!. Если же пользователь введёт отрицательное число или число побольше, то index окажется за пределами диапазона массива. В этом случае, поскольку мы просто выводим значение по индексу, результатом будет вывод мусора (при условии, что пользователь введёт число вне диапазона). Но в других случаях ложное предположение может привести и к изменениям значений переменных, и к сбою в программе.

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

Определение ложных предположений

Оказывается, мы можем найти почти все предположения, которые необходимо проверить в одном из следующих трёх мест:

   При вызове функции, когда caller может передать некорректные или семантически бессмысленные аргументы.

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

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

Поэтому, придерживаясь безопасного программирования, нужно следовать следующим трём правилам:

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

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

   Проверяйте данные ввода на соответствие ожидаемому типу данных и его диапазону.

Рассмотрим примеры проблем:

Проблема №1: При вызове функции caller может передать некорректные или семантически бессмысленные аргументы:

Можете ли вы определить потенциальную проблему здесь? Дело в том, что caller может передать нулевой указатель вместо допустимой строки C-style. Если это произойдёт, то в программе будет сбой. Вот как правильно (с проверкой параметра функции: не является ли он нулевым):

Проблема №2: Возвращаемое значение может указывать на возникшую ошибку:

Можете ли вы определить потенциальную проблему здесь? Пользователь может ввести символ, который не находится в строке hello. Если это произойдёт, то функция find() возвратит индекс -1, который и выведется.

Правильно:

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

Вот как правильно (с проверкой пользовательского ввода):

Обратите внимание, здесь проверка двухуровневая:

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

   Во-вторых, это значение должно находиться в диапазоне массива.

Обработка ложных предположений


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

Но всё же есть несколько способов обработки ложных предположений:

Способ №1: Пропустите код, который зависит напрямую от правильности предположения:

В примере выше, если cstring окажется NULL, то мы ничего не будем выводить. Мы пропустили тот код, который напрямую зависит от значения cstring и который с ним работает (в коде мы просто выводим этот cstring). Это может быть хорошим вариантом, если пропущенный стейтмент не является критическим и не влияет на логику программы. Основной недостаток при этом состоит в том, что caller или пользователь не имеет возможности определить, что что-то пошло не так.

Способ №2: В функции возвращайте код ошибки обратно в caller и позволяйте caller-у обработать эту ошибку:

Здесь функция возвратит -1, если caller передаст некорректный index. Возврат перечислителя, в качестве кода ошибки, будет ещё лучшим вариантом.

Способ №3: Если нужно немедленно завершить программу, то используйте функцию exit(), которая находится в заголовочном файле cstdlib для возврата кода ошибки обратно в операционную систему:

Если caller передаст некорректный index, то программа немедленно завершит своё выполнение и передаст код ошибки 2 обратно в операционную систему.

Способ №4: Если пользователь ввёл данные не того типа, что нужно — попросите пользователя ввести данные ещё раз:

Способ №5: Ещё одним механизмом для вывода сообщений об ошибках является cerr. cerr — это объект вывода (как и cout), который находится в заголовочном файле iostream и выводит сообщения об ошибках в консоль (как и cout), но только эти сообщения можно ещё и перенаправить в отдельный файл об ошибках. Т.е. основное различие cerr от cout заключается в том, что cerr целенаправленно используется для вывода сообщений об ошибках, тогда как cout для всего остального. Например:

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

Способ №6: Если вы работаете в какой-то графической среде (например: MFC, SDL, QT и т.д.), то распространённой практикой является вывод сплывающего окна с кодом ошибки, а за ним немедленное завершение программы. То, как это сделать, зависит конкретно от среды разработки.

Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (60 оценок, среднее: 4,93 из 5)
Загрузка...

Комментариев: 1

  1. Аватар Александр:

    а где же throw-try-catch? или дальше будет?
    вроде как логичный и удобный способ как сообщать об ошибках, так и "ловить" их…

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

Ваш E-mail не будет опубликован. Обязательные поля помечены *