Как динамически создать перечисление во время компиляции для моей структуры - PullRequest
1 голос
/ 20 марта 2020

У меня есть эта структура ниже

struct foo {
    char *name;
    int (*validate)(u8_t *data, size_t size);
    u8_t value;
    u8_t changed;
    foo_id id;
};
typedef struct foo foo_t;

I wi sh I для создания массива foo_t во время компиляции через определения, например:

int my_validate(u8_t *data, size_t size) {...}

FOO_CREATE(my_name, my_validate, 0, 0);
FOO_CREATE(my_name2, NULL, 0, 0);

и при компиляции Если результат будет:

enum {
    MY_NAME_FOO = 0,
    MY_NAME2_FOO,
    FOO_COUNT
} foo_id;

static foo_t foo[FOO_COUNT] = {
    {
        .name = "my_name",
        .validate = my_validate,
        .value = 0,
        .changed = 0,
        .id = MY_NAME_FOO       
    },
    {
        .name = "my_name2",
        .validate = NULL,
        .value = 0,
        .changed = 0,
        .id = MY_NAME2_FOO       
    },
}

Если это было невозможно только с помощью C и cmake во время компиляции, что вы предлагаете мне сделать эту работу?

1 Ответ

1 голос
/ 20 марта 2020

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


Файл со всеми вызванными макросами

Прежде всего вам нужно поместите все ваши вызовы макросов в один файл. Вы можете дать ему имя и желаемое расширение: например, классическое расширение .h или что-то с описательным расширением, например .def.

Итак, PreprocessorTypePopulation.h можно определить следующим образом:

FOO_CREATE(my_name, my_validate, 0, 0)
FOO_CREATE(my_name2, NULL, 0, 0)

Содержит все вызванные макросы FOO_CREATE.

Примечание : после запятой нет запятых или точек с запятой вызов макроса. Также реализация с запятыми (удаление их из макросов) работала бы в этом случае (потому что были задействованы только элементы enum и элементы массива).

Файл, содержащий сгенерированные struct / enums:

Это может быть файл .h. В моем примере это файл C, содержащий фиктивную демонстрацию main(). Я только что преобразовал int типы OP в типы, содержащиеся в stdint.h.

#include <stddef.h>
#include <stdint.h>

#ifdef FOO_CREATE
#undef FOO_CREATE
#endif

/* Enum creation macro */
#define FOO_CREATE(nm,func,val,chgd) nm##_FOO,

typedef enum {
#include "PreprocessorTypePopulation.h"
    FOO_COUNT
} foo_id;

struct foo {
    char *name;
    int (*validate)(uint8_t *data, size_t size);
    uint8_t value;
    uint8_t changed;
    foo_id id;
};
typedef struct foo foo_t;

int my_validate(uint8_t *data, size_t size)
{
  return 0;
}

#undef FOO_CREATE

/* Array creation macro */
#define FOO_CREATE(nm,func,val,chgd) \
  {                                  \
    .name = (char *) #nm,            \
    .validate = func,                \
    .value = val,                    \
    .changed = chgd,                 \
    .id = nm##_FOO                   \
  },

static foo_t foo[FOO_COUNT] = {
 #include "PreprocessorTypePopulation.h"
};

int main(void)
{
  return 0;
}

Как видите, реализована следующая стратегия:

  1. Undef любое предыдущее FOO_CREATE() определение
  2. Определить макрос FOO_CREATE() для первого задания (перечислительное создание)
  3. Включить .def файл INSIDE перечисление. Последовательность FOO_CREATE() s будет использоваться для генерации элементов перечисления в соответствии с только что определенным макросом
  4. Снова отмените определение макроса и переопределите его для второй задачи (определение массива структур)
  5. Включите .def файл INSIDE определение массива. Последовательность FOO_CREATE() с будет использоваться для генерации элементов массива в соответствии с только что определенным макросом

-

Вывод

, скомпилированный с помощью * Опция 1064 * только для препроцессора , в моем случае с опцией

gcc PreprocessorTypePopulation.c -E -P 

(опция -P удаляет линейных маркеров из вывода), а затем я получил следующий вывод (я просто убрал все вещи, относящиеся к включенным стандартным заголовкам):

typedef enum {
my_name_FOO,
my_name2_FOO,
    FOO_COUNT
} foo_id;

struct foo {
    char *name;
    int (*validate)(short *data, int size);
    short value;
    short changed;
    foo_id id;
};
typedef struct foo foo_t;

int my_validate(short *data, int size)
{
  return 0;
}

static foo_t foo[FOO_COUNT] = {
{ .name = "my_name", .validate = my_validate, .value = 0, .changed = 0, .id = my_name_FOO },
{ .name = "my_name2", .validate = NULL, .value = 0, .changed = 0, .id = my_name2_FOO },
}

int main(void)
{
  return 0;
}

-

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

...