Коды ошибок в си

Обработка ошибок в C

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

Все коды ошибок имеют положительные значения, и могут использоваться в директивах препроцессора #if. В целях удобства и переносимости заголовочный файл <errno. h> определяет макросы, соответствующие кодам ошибок.

Стандарт ISO C определяет следующие коды:

Нехитрый скрипт печатает в консоль коды ошибок, их символические имена и описания:

Функции работы с errno

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

<stdio. h>

void perror(const char *s);

<string. h>

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

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

size_t strerrorlen_s(errno_t errnum);

errno_t strerror_s(char *buf, rsize_t buflen, errno_t errnum);

Функции входят в Annex K (Bounds-checking interfaces), вызвавший много споров. Он не обязателен к выполнению и целиком не реализован ни в одной из свободных библиотек. Open Watcom C/C++ (Windows), Slibc (GNU libc) и Safe C Library (POSIX), в последней, к сожалению, именно эти две функции не реализованы. Тем не менее, их можно найти в коммерческих средах разработки и системах реального времени, Embarcadero RAD Studio, INtime RTOS, QNX.

Стандарт POSIX.1-2008 определяет следующие функции:

char *strerror_l(int errnum, locale_t locale);

int strerror_r(int errnum, char *buf, size_t buflen);

Увы, никакого аналога strerrorlen_s() в POSIX не определили, поэтому длину строки можно выяснить лишь экспериментальным путём. Обычно 300 символов хватает за глаза. GNU C Library в реализации strerror() использует буфер длиной в 1024 символа. Но мало ли, а вдруг?

Макрос assert()

<assert. h>

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

<stdlib. h>

int atexit(void (*func)(void));

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

_Noreturn void exit(int exit_code);

_Noreturn void abort(void);

Функции setjmp() и longjmp()

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

<setjmp. h>

int setjmp(jmp_buf env);

void longjmp(jmp_buf env, int value);

Используя setjmp() и longjmp () можно реализовать механизм исключений. Во многих языках высокого уровня (например, в Perl) исключения реализованы через них.

Внимание! Функции setjmp() и longjmp () в первую очередь применяются в системном программировании, и их использование в клиентском коде не рекомендуется. Их применение ухудшает читаемость программы и может привести к непредсказуемым ошибкам. Например, что произойдёт, если вы прыгните не вверх по стеку – в вызывающую функцию, а в параллельную, уже завершившую выполнение?

Обработка ошибок: Исключения

Обработка ошибок Исключения в c++ c

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

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

Почему исключения?

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

Следующий вопрос, который у вас мог возникнуть: «А как организовать передачу информации о контексте, в котором произошла ошибка»? Когда мы использовали простые коды ошибок, у нас для этого было предусмотрено перечисление. Каждый элемент перечисления соответствовал тому или иному типу ошибки. Этот вариант широко распространен и неплохо работает, но он имеет свои ограничения. Например, с помощью одного лишь кода ошибки у нас нет возможности передать дополнительную информацию, чтобы уточнить причину ошибки. В случае же с исключениями у вас появляется выбор. Рассмотрим несколько наиболее очевидных вариантов.

Вариант 1. Коды ошибок. Опять

Мы ведь хотел уйти от кодов ошибок, а тут снова они? — В какой-то мере да. Однако на этот раз мы будем возвращать код не через явные выходные параметры функции, а с помощью экземпляра исключения:

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

Заметим однако, что в более сложных случаях мы все же можем получить некую выгоду:

Вариант 2. Иерархия исключений

Если уж мы пишем ООП-код, то почему бы и ошибки не сделать объектами? Переход от кодов ошибок к иерархии классов осуществить не так уж сложно:

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

Вариант 3. Текстовые сообщения

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

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

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

Коды ошибок, иерархия или текстовые сообщения?

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

Это лишь один из возможных подходов к созданию подобного класса исключения, и его можно улучшить. Но даже представленная реализация вполне неплохо справляется с комбинированием лучших качеств из рассмотренных нами вариантов использования исключений. В случае необходимости, чтобы возбудить исключение, мы передаем предопределенный код ошибки, которому соответствует шаблон текстового сообщения. Сами коды ошибок в этом случае мы определили внутри класса исключения, поскольку они непосредственно с ним связаны. Значения кодов ошибок в перечислении начинаются явным образом с 1000. Это поможет нам в дальнейшем не запутаться, если для других классов исключений мы будем определять коды в других диапазонах. Например, ошибки работы с БД мы можем определить, начав с 2000, а для сетевых соединений — с 3000.

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

Коротко об освобождении ресурсов

Коды ошибок в си

// Не следует так делать…
if (timer. done)
// Одиночному оператору нужны скобки!
timer. control = TIMER_RESTART;

ПРАВИЛО #2 – Ключевое слово «const»

Ключевое слово const следует использовать:

— для объявления переменных, которые не должны меняться после инициализации,
— для определения передаваемых по ссылке параметров функции, которые не могут быть изменены,
— для определения полей в структурах и объединениях, которые не могут быть изменены
— в качестве альтернативы директиве #define при определении числовых констант.

const unsigned char dirPort = 0xff;

ПРАВИЛО #3 – Ключевое слово «static»

static void InnerFunction( void )
<
….
>

ПРАВИЛО #4 – Ключевое слово «volatile»

Ключевое слово volatile нужно использовать везде, где уместно, включая:

— Объявление глобальной переменной доступной любому обработчику прерываний,
— Объявление глобальной переменной доступной двум или более задачам,
— Объявление указателя к отображаемым в памяти периферийным регистрам ввода/вывода

volatile unsigned int timer;

ПРАВИЛО #5 – Комментарии

// Так делать нельзя.
/*
a = a + 1;
/* comment */
b = b + 1;
*/

// Так правильно.
#if 0
a = a + 1;
/* comment */
b = b + 1;
#endif

ПРАВИЛО #6 – Тип данных с фиксированной разрядностью

ПРАВИЛО #7 – Поразрядные операторы

, ^, <<, и >>) не должен использоваться для управления целочисленными данными со знаком.

ПРАВИЛО #8 – Целые числа со знаком и без

ПРАВИЛО #9 – Параметризированные макросы против inline функций

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

ПРАВИЛО #10 – Оператор-запятая

Оператор запятая (,) не должен использоваться внутри описания переменной.

// Так делать нельзя…
char * x, y; // вы хотите, чтобы «y» был указателем, или нет?

СНОСКИ

Michael Barr «Bug-Killing Coding Standard Rules for Embedded C». Вольный перевод ChipEnable. Ru

Comments

*ушел переписывать все параметризирова нные макросы в инлайн функции*

давно взял себе за правило всегда объявлять все переменные с фиксированной разрядностью (uint8_t, etc.)

Кто знает что есть такая книга :-) хорошо что есть такой сайт! Где можно узнать про книги и не только.

Источники:

https://habr. com/ru/post/324642/

https://tech-geek. ru/error-handling-exceptions-in-c/

https://chipenable. ru/index. php/item/62

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

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