То, что я собираюсь предложить вам, - это то, что я действительно видел в действительно большом производственном проекте. Я должен сказать это, потому что я признаю, что это не красиво выглядящее решение.
Файл со всеми вызванными макросами
Прежде всего вам нужно поместите все ваши вызовы макросов в один файл. Вы можете дать ему имя и желаемое расширение: например, классическое расширение .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;
}
Как видите, реализована следующая стратегия:
- Undef любое предыдущее
FOO_CREATE()
определение - Определить макрос
FOO_CREATE()
для первого задания (перечислительное создание) - Включить
.def
файл INSIDE перечисление. Последовательность FOO_CREATE()
s будет использоваться для генерации элементов перечисления в соответствии с только что определенным макросом - Снова отмените определение макроса и переопределите его для второй задачи (определение массива структур)
- Включите
.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;
}
-
В заключение , это не обязательно красивое решение. Но работает и предотвращает множество человеческих ошибок, концентрируя несколько определений в одном файле. В долгосрочном крупном проекте это может сэкономить недели работы.