Какой хороший справочник по шаблонам использования X-макросов в C (или, возможно, C++)?

Основное определение, пример и несколько ссылок для "X-макросы" даны в этом запись в Википедии о препроцессоре C:

An X-Macro is a header file (commonly using a ".def" extension instead of the traditional ".h") that contains a list of similar macro calls (which can be referred to as "component macros").

Каковы хорошие источники информации о том, как использовать эту мощную технику? Существуют ли известные библиотеки с открытым исходным кодом, использующие этот метод?

Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
20
0
6 353
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

У доктора Добба есть статья по этому поводу.

У меня есть этот выпуск cuj, и он постоянно открыт для этой статьи.

luser droog 24.02.2012 08:41
Ответ принят как подходящий

Я часто использую X Macros () в коде. Значение исходит только от добавления новых данных только в «список X» и без изменения какого-либо другого кода.

Чаще всего X Macros () используется для связывания текста ошибки с кодами ошибок. Когда добавляются новые коды ошибок, программисты должны не забывать добавлять код и текст, как правило, в разных местах. X Macro позволяет добавлять новые данные об ошибках в одном месте и автоматически заполнять их везде, где это необходимо.

К сожалению, в этих механизмах используется множество магических средств предварительной компиляции, которые могут затруднить чтение кода (например, соединение строк с помощью token1##token2, создание строк с помощью #token). Из-за этого я обычно объясняю, что делает X Macro, в комментариях.

Вот пример использования значений ошибки / возврата. Все новые данные добавляются в список "X_ERROR". Ни один из остальных кодов изменять не нужно.

/* 
 * X Macro() data list
 * Format: Enum, Value, Text
 */
#define X_ERROR \
  X(ERROR_NONE,   1, "Success") \
  X(ERROR_SYNTAX, 5, "Invalid syntax") \
  X(ERROR_RANGE,  8, "Out of range")

/* 
 * Build an array of error return values
 *   e.g. {0,5,8}
 */
static int ErrorVal[] =
{
  #define X(Enum,Val,Text)     Val,
   X_ERROR
  #undef X
};

/* 
 * Build an array of error enum names
 *   e.g. {"ERROR_NONE","ERROR_SYNTAX","ERROR_RANGE"}
 */

static char * ErrorEnum[] = {
  #define X(Enum,Val,Text)     #Enum,
   X_ERROR
  #undef X
};

/* 
 * Build an array of error strings
 *   e.g. {"Success","Invalid syntax","Out of range"}
 */
static char * ErrorText[] = {
  #define X(Enum,Val,Text)     Text,
   X_ERROR
  #undef X
};

/* 
 * Create an enumerated list of error indexes
 *   e.g. 0,1,2
 */
enum {
  #define X(Enum,Val,Text)     IDX_##Enum,
   X_ERROR
  #undef X
  IDX_MAX   /* Array size */
};

void showErrorInfo(void)
{
    int i;

    /* 
     * Access the values
     */
    for (i=0; i<IDX_MAX; i++)
        printf(" %s == %d [%s]\n", ErrorEnum[i], ErrorVal[i], ErrorText[i]);

}

Вы также можете использовать X Macros () для генерации кода. Например, чтобы проверить, является ли значение ошибки «известным», макрос X может генерировать случаи в операторе switch:

 /*
  * Test validity of an error value
  *      case ERROR_SUCCESS:
  *      case ERROR_SYNTAX:
  *      case ERROR_RANGE:
  */

  switch(value)
  {

  #define X(Enum,Val,Text)     case Val:
   X_ERROR
  #undef X
         printf("Error %d is ok\n",value);
         break;
      default:
         printf("Invalid error: %d\n",value);
         break;
  }

Очень, очень умно - это поможет не попасть в тиски зла, это улучшит как ремонтопригодность, так и удобочитаемость. Спасибо, JayG!

Adam Liss 23.10.2009 02:38

Забавно, я сам придумал похожий подход. Одна вещь, которую я обнаружил, заключается в том, что вам даже не нужно все это. Ссылаясь на ваш пример, вам даже не нужно #define или X_ERROR; вместо этого сделайте несколько строк "недействительных" X(). Затем я назвал файл, содержащий его, с помощью .x в конце (вместо, например, .h).

Andrew 04.02.2021 05:55

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

/* declare an enumeration of state codes */
enum{ STATE0, STATE1, STATE2, ... , STATEX, NUM_STATES};

/* declare a table of function pointers */
p_func_t jumptable[NUM_STATES] = {func0, func1, func2, ... , funcX};

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

Мой друг познакомил меня с X-макросами, и это было похоже на то, как в моей голове загорелась лампочка. Серьезно, где вы всю жизнь были икс-макросы!

Итак, теперь я определяю следующую таблицу:

#define STATE_TABLE \
        ENTRY(STATE0, func0) \
        ENTRY(STATE1, func1) \
        ENTRY(STATE2, func2) \
        ...
        ENTRY(STATEX, funcX) \

И я могу использовать это следующим образом:

enum
{
#define ENTRY(a,b) a,
    STATE_TABLE
#undef ENTRY
    NUM_STATES
};

и

p_func_t jumptable[NUM_STATES] =
{
#define ENTRY(a,b) b,
    STATE_TABLE
#undef ENTRY
};

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

#define ENTRY(a,b) static void b(void);
    STATE_TABLE
#undef ENTRY

Другое использование - объявление и инициализация регистров.

#define IO_ADDRESS_OFFSET (0x8000)
#define REGISTER_TABLE\
    ENTRY(reg0, IO_ADDRESS_OFFSET + 0, 0x11)\
    ENTRY(reg1, IO_ADDRESS_OFFSET + 1, 0x55)\
    ENTRY(reg2, IO_ADDRESS_OFFSET + 2, 0x1b)\
    ...
    ENTRY(regX, IO_ADDRESS_OFFSET + X, 0x33)\

/* declare the registers (where _at_ is a compiler specific directive) */
#define ENTRY(a, b, c) volatile uint8_t a _at_ b:
    REGISTER_TABLE
#undef ENTRY

/* initialize registers */
#def ENTRY(a, b, c) a = c;
    REGISTER_TABLE
#undef ENTRY

Однако мое любимое использование - это обработчики связи.

Сначала я создаю таблицу связи, содержащую имя и код каждой команды:

#define COMMAND_TABLE \
    ENTRY(RESERVED,    reserved,    0x00) \
    ENTRY(COMMAND1,    command1,    0x01) \
    ENTRY(COMMAND2,    command2,    0x02) \
    ...
    ENTRY(COMMANDX,    commandX,    0x0X) \

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

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

typedef struct {...}command1_cmd_t;
typedef struct {...}command2_cmd_t;

etc.

Точно так же я определяю структуры для каждого ответа на команду:

typedef struct {...}response1_resp_t;
typedef struct {...}response2_resp_t;

etc.

Затем я могу определить перечисление кода моей команды:

enum
{
#define ENTRY(a,b,c) a##_CMD = c,
    COMMAND_TABLE
#undef ENTRY
};

Я могу определить перечисление длины моей команды:

enum
{
#define ENTRY(a,b,c) a##_CMD_LENGTH = sizeof(b##_cmd_t);
    COMMAND_TABLE
#undef ENTRY
};

Я могу определить свое перечисление длины ответа:

enum
{
#define ENTRY(a,b,c) a##_RESP_LENGTH = sizeof(b##_resp_t);
    COMMAND_TABLE
#undef ENTRY
};

Я могу определить количество команд следующим образом:

typedef struct
{
#define ENTRY(a,b,c) uint8_t b;
    COMMAND_TABLE
#undef ENTRY
} offset_struct_t;

#define NUMBER_OF_COMMANDS sizeof(offset_struct_t)

ПРИМЕЧАНИЕ. Я никогда не создаю экземпляр offset_struct_t, я просто использую его как способ для компилятора сгенерировать для меня мое количество команд.

Обратите внимание, что я могу сгенерировать свою таблицу указателей функций следующим образом:

p_func_t jump_table[NUMBER_OF_COMMANDS] = 
{
#define ENTRY(a,b,c) process_##b,
    COMMAND_TABLE
#undef ENTRY
}

И мои прототипы функций:

#define ENTRY(a,b,c) void process_##b(void);
    COMMAND_TABLE
#undef ENTRY

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

/* reminder the sizeof a union is the size of its largest member */
typedef union
{
#define ENTRY(a,b,c) uint8_t b##_buf[sizeof(b##_cmd_t)];
    COMMAND_TABLE
#undef ENTRY
}tx_buf_t

Опять же, это объединение похоже на мою структуру смещения, оно не создается, вместо этого я могу использовать оператор sizeof для объявления моего размера буфера передачи.

uint8_t tx_buf[sizeof(tx_buf_t)];

Теперь мой буфер передачи tx_buf имеет оптимальный размер, и когда я добавляю команды к этому обработчику связи, мой буфер всегда будет оптимального размера. Здорово!

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