LINUX.ORG.RU

Возврат ошибки с контекстом в C

 


0

4

Язык C не имеет встроенного механизма работы с ошибками. Но работать с ошибками как-то нужно, значит это придётся делать вручную.

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

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

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

void
kill_running_instance
        (char *pid_file)
{
        FILE *f = fopen(pid_file, "r");
        if (f == NULL) {
                fprintf(stderr, "can not open file for reading: %s: %s\n", pid_file, strerror(errno));
                exit(1);
        }
        ....
}

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

void
kill_running_instance
        (bool *ok, char *pid_file)
{
        // reset error
        *ok = true;

        FILE *f = fopen(pid_file, "r");

        // indicate error
        if (f == NULL) {
                *ok = false;
                return;
        }

        ....
}

Так мы сможем извне узнать — функция нормально выполнила свою работу или что-то пошло неправильно.

Здесь появляется главная проблема. Как узнать что конкретно пошло неправильно? Хотя бы для того чтобы записать ошибку в лог и в консоль:

bool *ok;
if (kill_running_instance(&ok, pid_file), !ok) {
        // how can we find out what went wrong?
}

Это произошла ошибка открытия файла или ошибка чтения? Узнать это невозможно.

Небольшая заметка: посмотреть в errno не получится. Эта переменная используется для уточнения ошибки, а не полного описания ошибки. Например, strerror(errno) скажет Permission denied, а как извне узнать к чему это ошибка относится — всё ещё неизвестно.

Чтобы решить эту проблему, я придумал возвращать не bool *ok, а сообщение об ошибке и тип ошибки. Выглядит вот так:

void
kill_running_instance
        (struct error *e, char *pid_file)
{
        FILE *f = fopen(pid_file, "r");
        if (f == NULL) {
                ERROR_SET(e, SYSTEM, "can not open file for reading: %s: %s", pid_file, strerror(errno));
                return;
        }
        ....
}

....

struct error e;
INIT_ERROR(&e);
if (kill_running_instance(&e, pid_file), e.type != UNSET) {
        print_error(&e);
        deinit_error(&e);
        goto cleanup;
}

Контекст ошибки нам известен извне. Мы можем вывести её в консоль, в лог, отправить по почте, etc. Конечная цель выполнена!

Кстати, благодаря макросам известны имя файла и номер линии где был вызван ERROR_SET.

error определён так:

enum error_type {
        UNSET,

        /*
                Standard library function fails
        */
        SYSTEM,
};

struct error {
        enum error_type type;
        char *msg;
        char *filename;
        size_t line;
        bool is_set;
};

INIT_ERROR присваивает структуре дефолтные значения.

ERROR_SET раскрывает функцию, которая присваивает ошибке тип, сообщение, имя файла и номер линии где возникла ошибка. Стринги копируются, всё норм.

Теперь, собственно, вопросы.

  1. Много ли Open Source проектов, где работа с ошибками организована похожим образом? Буду благодарен, если покажете.

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

  3. Буду благодарен, если подскажите какие есть риски и недостатки в моём методе работы с ошибками.

Заранее спасибо.



Последнее исправление: elonmusk (всего исправлений: 7)
Ответ на: комментарий от i-rinat

А что если возвращать тип ошибки и сообщение об ошибке? По-моему, это простейшее и подходящее решение.

elonmusk
() автор топика
Ответ на: комментарий от Deleted

Абсолютно. Это называется «контекст». Люди владеют им, если у них, конечно, нет синдрома аспергера.

ugoday ★★★★★
()
Ответ на: комментарий от ugoday

Абсолютно.

Да ну? Мало того, что ты ответил. Так ты умудрился ответить на риторические вопросы. На риторические!

Deleted
()
Ответ на: комментарий от elonmusk

Если функция не знает что делать с ошибкой (напечатать, записать в лог, etc.), она должна вернуть её caller’у.

Это смешная банальность. Что за терминология? Знает/не знает. Как функция написана, так она и должна работать.

Трудно разговаривать с человеком, который просто так переходит на личности.

А что делать, если у вас реальная необъяснимая идиосинкразия?

vodz ★★★★★
()
Ответ на: комментарий от ugoday

как и было сказано

Да уж. Подумал и решил. Бывай.

ugoday

Deleted
()
Ответ на: комментарий от elonmusk

Вынести весь код, который может породить ошибки, в функцию main? Я походу не понял.

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

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

cleanup за тебя всё чистит. Это расширение gnuc. Погугли.

Либо я, опять же, ННП, либо идея создать единый код очистки – не работает.

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

Если ты используешь нормальные аллокаторы - такой проблемы вообще нет. Ничего записывать ненужно - просто сносишь весь хип нахрен.

Хочешь сделать надёжно - автоматизируй. Лог можно импрувить до бесконечности.

RibiKukan
()
Ответ на: комментарий от elonmusk

Кода ошибки зачастую достаточно.

Что ты будешь делать с сообщением? Если там будет какая-то динамически изменяющаяся информация, в какой-то момент ты начнёшь парсить строки. В одной части программы строки будут генерироваться, а в другой — парситься. И всё в рамках одного процесса. Абсурдно.

А если строки всегда неизменные, проще численные коды использовать. Их проверка будет быстрее, кода будет меньше.

i-rinat ★★★★★
()
Ответ на: комментарий от Miha

Потом он придет к тому

Копай глубже. Придёт он к тому, что язык должен быть «человечнее», как go.

Deleted
()

ШТА?!? o_O

Язык C не имеет встроенного механизма работы с ошибками. Но работать с ошибками как-то нужно, значит это придётся делать вручную.

Язабан. Пожизненно и на месте. Это уже даже не смешно в 2019г.

Дальше пассажа выше не читал. Боюсь, ТС хочет заново открыть робастное программирование.

Передайте ему, кто там поближе, что оно уже изобретено и давно.

Moisha_Liberman ★★
()
Ответ на: ШТА?!? o_O от Moisha_Liberman

Нахуй иди

anonymous
()
Ответ на: комментарий от i-rinat

А как же контекст? Например позиция ошибки при парсинге.

Я вообще слабо себе представляю задачи, в которых кода ошибки достаточно. Разве что нам без разницы что за ошибка была. Но тогда есть bool.

RazrFalcon ★★★★★
()
Ответ на: комментарий от RazrFalcon

В Linux (ядро) многие функции по задумке возвращающие целое неотрицательное число, ещё могут вернуть отрицательное. Тогда это считается ошибкой. Например, если памяти не хватило, возвращается -ENOMEM. Системные вызовы из userspace ведут себя так же.

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

Так что можно и число пропарсенных байт возвращать, и код ошибки, и всё в одном числе. Если в процессе работы памяти не хватило, то уже и не важно, сколько байт там обработано. Не получилось, и всё тут. Если получилось пропарсить сколько-то, а дальше не парсится, можно просто число вернуть. Вызывающая сторона сравнит результат с длиной переданной строки и сделает выводы.

Если всё же хочется много всякой информации, то придётся объявлять структуру и в ней наружу отдавать. Либо заполняя по указателю, либо как возвращаемое значение функции. Боль, конечно. Но это ж Си, тут всегда так. Боль заставляет задуматься, а действительно ли вот эту информацию так уж нужно возвращать. Если да, терпишь боль. Если нет, не возвращаешь. Закаляет характер.

i-rinat ★★★★★
()
Последнее исправление: i-rinat (всего исправлений: 2)
Ответ на: комментарий от i-rinat

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

Для эмбеда, где нужно экономить, может и норм. Но в реальной жизни хотелось бы подробных ошибок. У меня даже в простеньком xml парсере куча вариантов ошибок и как изображать на Си - не ясно.

RazrFalcon ★★★★★
()
Ответ на: комментарий от RazrFalcon

Тут даже на плюсах нормальную обработку ошибок так и не придумали

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

anonymous
()
Ответ на: ШТА?!? o_O от Moisha_Liberman

Нормально не ответить? Что делать-то?

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.