Есть ли лучший способ преобразовать коды ошибок в строку?

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

Я хочу дать им строковое представление кода. Однако я не могу придумать прагматичного способа сделать это. Макрос stringify (#) не будет работать, поскольку код ошибки, отправленный в функцию, неизвестен во время компиляции. Есть ли что-то помимо большого переключателя, которое могло бы решить эту проблему?

Для контекста: это мой маленький журнал ошибок; это последний шаг конвейера ошибок, если программа доберется сюда, произошла фатальная ошибка.

// Notes: We don't do a lot of error-checking in this function, beyond
// the stuff built into PushNotification. The reason for that is
// fairly simple; at this point, we don't give a darn. If the program
// gets here, something's irreversibly wrong regardless. 
// Last edit: July 3rd, 2024
_Noreturn void InternalLogError(const char* caller, const char* file, u32 line,
                      u32 code)
{

    char error_message[128];
    // This is the place in which I want to add the code-to-string bit.
    snprintf(error_message, 128,
             "\nMemphim failed catastrophically.\n"
             "Code: %d.\nCaller: %s @ %s\nLine: %d\n",
             code, caller, file, line);

    if (CheckShellAvailability()) puts(error_message);
    else PushNotification("Memphim Error Reporter", error_message);

    exit(EXIT_FAILURE);
}

Почему в вашей системе есть ошибки, неизвестные во время компиляции? Наверняка вы знаете список всех возможных ошибок, которые могут произойти?

Lundin 04.07.2024 08:36

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

Zenais 04.07.2024 20:20

@Zenais Зачем использовать "%d" с u32?. Я бы порекомендовал int, "%d" для code и unsigned, "%u" для line.

chux - Reinstate Monica 05.07.2024 08:23

@chux-ReinstateMonica Ну, code не может быть отрицательным, поэтому беззнаковое значение кажется здесь наиболее подходящим, хотя я согласен с использованием модификаторов формата.

Zenais 05.07.2024 08:27

@chux-ReinstateMonica Прошу прощения, я не совсем понимаю, что вы имеете в виду. Под типом вы имеете в виду 32-битное целое число без знака?

Zenais 05.07.2024 08:36

@chux-ReinstateMonica Прежде чем реализовать ответ @Lundin, я просто передал целочисленный литерал, определенный препроцессором; то есть #define failed_syscall 1 и тому подобное. Теперь я передаю значение перечисления, получившее название ErrorType.

Zenais 05.07.2024 08:45

@Zenais Хорошо, с подходом Лундина code должно быть типа err_t. При вашем подходе code должен быть любым типом в диапазоне всех целочисленных констант, сформированных на основе ваших определений ошибок.

chux - Reinstate Monica 05.07.2024 08:51
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
7
137
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Таблица поиска строк может быть одним из очевидных способов сделать это.

typedef enum
{
  ERR_NONE,
  ERR_SERVERS_ON_FIRE,
  ERR_LIVE_BEAVERS,

  ERR_N // total number of supported errors
} err_t;

static const char* err_str[] = 
{
  [ERR_NONE]             = "No error",
  [ERR_SERVERS_ON_FIRE]  = "The servers are on fire",
  [ERR_LIVE_BEAVERS]     = "Live beavers in the server room",
};

static_assert(sizeof(err_str)/sizeof(*err_str) == ERR_N, 
              "err_t and err_str are not consistent with each other");

...

void InternalLogError(..., err_t code)
{
  puts(err_str[code]);
}

Или, если необходимо избегать хранения значений в отдельных местах, версия X-макроса:

#define ERR_LIST(X)                                        \
/*  code                 str                           */  \
  X(ERR_NONE,            "No error")                       \
  X(ERR_SERVERS_ON_FIRE, "The servers are on fire")        \
  X(ERR_LIVE_BEAVERS,    "Live beavers in the server room")

typedef enum
{
  #define ERR_T_ENUM(code, str) code,
  ERR_LIST(ERR_T_ENUM)
  
  ERR_N // total number of supported errors
} err_t;

static const char* err_str[] = 
{
  #define ERR_STR_LUT(code, str) [code] = str,
  ERR_LIST(ERR_STR_LUT)
};

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

Zenais 04.07.2024 20:21

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

#include <errorgen.h>
int some_library_function() {
     return ERROR_GEN(ERROR_SOME_ERROR, "This is error description")
}

Перед компиляцией сценарий CMake или Python просматривал все файлы. Для каждого файла, содержащего ERROR_GEN, было создано два файла — errorgen_list.h и errorgen_str.h. errorgen_list.h содержал все перечисления, собранные из ERROR_GEN функций. errorgen_str.h содержал "This is error description", таблицу для преобразования кода ошибки в строку. Скрипт выглядит примерно так:

def get_all_errors_from_sources():
    rereplace = re.compile(
        r'ERROR_GEN\s*\(\s*(.*)\s*,\s*(".*")\s*\)\s*;',
        flags=re.MULTILINE,
    )
    dir = os.path.dirname(__file__)
    errors: List[Err] = []
    for path in Path(dir).glob("**/*.c"):
        for line in open(path).read():
            res = rereplace.findall(line)
            if res:
                errors += [Err(*res)]
    return errors

def prepare_sources(errors):
    enumout = "/* @file */\n"
    for k, v in errors:
        enumout += "\t" + k + ",  /// " + v + "\n"
    msgout = "/* @file */\n"
    for k, v in errors:
        msgout += "\t" + v + ",  // " + k + "\n"
    return (enumout, msgout)

errors = get_all_errors_from_sources()
enumout, msgout = prepare_sources(errors)
with Path("errorgen_list.h", "w") as f:
    f.write(enumout)
with Path("errorgen_str.h", "w") as f:
    f.write(msgout )

Затем со следующим errorgen.h:

 enum errorgen_errors_e {
    ERRORGEN_OK = 0,
    ERRORGEN_START = -1000,
 #include "errorgen_list.h"
    ERRORGEN_STOP,
 };

 #define ERROR_GEN(a, b)  a

 const char *error_to_str(int);

И следующее errorgen.c:

 static const char *const errorsgen_str[] = {
 #include "errorgen_str.h"
 };

 const char *error_to_str(int err) {
     // error handling...
    return errorsgen_str[err - ERRORGEN_START - 1];
 }
  

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

«Я решил, что определение кода ошибки и текста ошибки в нескольких местах не для меня». В этом случае пример в опубликованном мной ответе может быть сгенерирован из X-макросов. То есть #define ERROR_LIST(X) X(0, ERR_NONE, "No error") и так далее. Я не публиковал такой ответ, потому что это не было требованием макросов OP и X, что может немного сбить с толку.

Lundin 04.07.2024 11:04

Я добавил в свой ответ версию X-макроса.

Lundin 04.07.2024 11:11

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

// errors.tbl - NB: no header guards!

#ifdef ERR_AS_STR
const char *errStrs[];
static const char *errToStr( int code ) { return errStrs[code]; }
static const char *errStrs[] = {
#   define X(e,s) s
#else
enum {
#   define X(e,s) e
#endif

    X( ERR_NONE,            "No error" ),
    X( ERR_SERVERS_ON_FIRE, "The servers are on fire" ),
    X( ERR_LIVE_BEAVERS,    "Live beavers in the server room" ),
    X( ERR_N, NULL )
#   undef X
};

и

// main.c

#include <stdio.h>

#include "errors.tbl" // once as enums
#define ERR_AS_STR
#include "errors.tbl" // and now as array of strings (and getter)
#undef ERR_AS_STR

int main( void ) {
    for( int i = ERR_NONE; i < ERR_N; i++ ) puts( errToStr( i ) );
    return 0;
}

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

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

Zenais 05.07.2024 07:23

@Zenais Если хочешь, можешь использовать #include "borkum.riff". Это просто имя файла... Я использовал .tbl («таблица»), чтобы указать, что его содержимое и назначение (включено дважды) отличаются от типичного .h файла... Существуют соглашения и ожидания относительно расширений имен файлов, известные редакторам, компиляторам и человеческие читатели. В данном случае я выбрал имя .tbl; ничего больше. Ваше здоровье! :-)

Fe2O3 05.07.2024 07:58

Оххх, окей! Это имеет смысл, на большинстве других языков, которые я использовал, это было именно так, понятия не имею, почему я думал, что в C все будет иначе. Спасибо :)

Zenais 05.07.2024 08:01

Другие вопросы по теме