Как захватить имена макросов в C?

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

#define EVENT_X 0
#define EVENT_XYZ 1
#define EVENT_123 2
...

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

Конкретно: как я могу создать функцию event_name(int event_value), которая отображает имя события, чтобы:

event_name(EVENT_X);
event_name(1);

дисплеи

"X"
"XYZ"

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

const char* EVENT_NAMES[] = {
  "X",
  "XYZ",
  "123",
  ... }

в этом случае моя функция event_name может быть просто:

void event_name(int event_value) {
  printf("%s\n",EVENT_NAMES[event_value]);
}

Но с сотнями макросов создавать вручную этот массив EVENT_NAMES сложно и подвержено ошибкам.

Несколько более надежным решением является создание этого массива следующим образом:

const char* EVENT_NAMES[] = {
  STRINGIFY(EVENT_X),
  STRINGIFY(EVENT_XYZ),
  ...
}

с #define STRINGIFY(X) #x

Но это означает удаление во время выполнения «EVENT_», и мне по-прежнему нужно повторно перечислять все макросы вручную при создании EVENT_NAMES, рискуя забыть некоторые макросы или переставить их.

Есть ли лучшее решение?

Как насчет использования перечисления вроде enum EVENT {X, XYZ};, а затем просто STRINGIFY(XYZ);?

Joel 21.04.2023 13:00

ваш первый способ - это то, как это реально делается

user253751 21.04.2023 13:03

Если есть пробелы (например, нет события 5, но есть 4 и 6), вы можете сделать это так const char *EVENT_NAMES[] = { [EVENT_X] = STRINGIFY(EVENT_X), [EVENT_Y] = STRINGIFY(EVENT_Y) };

lulle2007200 21.04.2023 13:10

Должны ли они использовать макросы препроцессора #defined или перечисления будут работать?

Lundin 21.04.2023 13:37

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

dalfaB 21.04.2023 13:37

Вы делаете это, как в вашем первом описании, но вы не делаете это «вручную». Ваша система сборки генерирует массив для вас.

William Pursell 21.04.2023 14:09

почему вы не можете использовать static map <int, const char*> вместо того, чтобы считать индекс массива значением макроса. Вы можете заполнить эту карту при запуске, и она также не будет подвержена ошибкам.

Yogesh 21.04.2023 14:22

@Yogesh Потому что: неправильный язык программирования. Кроме того, даже если C++ был вариантом, то std::map использует выделение кучи во время выполнения и, как правило, чертовски медленный по сравнению с макросами времени компиляции и LUT прямого доступа.

Lundin 21.04.2023 14:57

вам нужна логика за горизонтом макросов - поэтому, если вы настаиваете на том, чтобы делать это с помощью макросов, я думаю, вы проиграли. Но если нет, ваша библиотека может предоставить ENUM, содержащий все значения (почти как макросы для пользователя) и обернуть создание значений перечисления макросом CREATE_EVENT(имя,значение) в заголовочном файле для пользователя и другим реализация этого же макроса для внутреннего использования библиотек (опять же источник файла включения библиотеки), чтобы действительно дополнительно создать назначения строк, например. в библиотеке init().

Synopsis 21.04.2023 17:19

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

Synopsis 21.04.2023 17:19
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
10
79
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

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

В C невозможно одновременно определить значения перечисления и массив, описывающий эти значения.

Но определить все без риска несоответствий все же возможно.
У нас есть препроцессор, который работает с текстами и может читать файлы. Сначала создайте файл с именем, например, «events.inl» (на самом деле это не заголовок или файл кода), файл должен содержать только такие строки, как:

DEF_EVENT(   X, 0  )          // EVENT_ prefix must not be here
DEF_EVENT( XYZ, 1  )
DEF_EVENT( 123, 12 )

Затем мы добавим этот файл, чтобы сгенерировать перечисление где-то и массив где-то еще:

// define events as enum, all events are named EVENT_...
//======================
#define DEF_EVENT(name,value) EVENT_##name = value,
enum {
    #include "events.inl"
};
#undef DEF_EVENT

// the array for user access
//==========================
#define DEF_EVENT(name,value) [value] = #name,
const char* const  EVENT_NAMES[] = {
    #include "events.inl"
};
#undef DEF_EVENT

Если для значения нет события, в массиве есть указатель NULL.

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

Редактировать : Мы можем избежать файла, используя большой макрос, и просто заменить #include(s) этим макросом:

#define ALL_DEF_EVENTS    \
  DEF_EVENT(   X, 0  )    \
  DEF_EVENT( XYZ, 1  )    \
  DEF_EVENT( 123, 12 )

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

Lolo 21.04.2023 15:07

Техника называется x-macros. Вы можете поискать это. Википедия X Macro например.

Jonathan Leffler 22.04.2023 07:51

@JonathanLeffler, спасибо за ссылку. Внутри мы можем увидеть, как просто адаптировать мой образец, чтобы избежать этого неприятного промежуточного файла.

dalfaB 22.04.2023 12:57

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