Можно ли использовать #include.c исходный файл для поддержки встроенного кода C?
Я не специалист по программированию C, и я знаю, что исходный файл .c
из другого считается плохой практикой, но у меня есть ситуация, когда я думаю, что это может помочь в ремонтопригодности.
У меня большая структура с большим количеством элементов, и я использую #define
для хранения индексов.
#define TOTO_IND 0
#define TITI_IND 1
…
#define TATA_IND 50
static const MyElements elems [] = {
{"TOTO", 18, "French"},
{"TITI", 27, "English"},
...,
{"TATA", 45, "Spanish"}
}
Поскольку мне нужно получить доступ к структуре из индекса, мне нужно сохранить #define
и объявление структуры синхронизированным. Это означает, что я должен вставить новые элементы в нужное место и соответственно обновить #define
.
Это склонность к ошибкам, и мне это не нравится (но для оценки производительности я не нашел лучшего решения).
Во всяком случае, этот файл также содержит множество функций для обработки этой структуры. Я также хочу сохранить разделение кода и избежать глобальных переменных.
Чтобы сделать вещи "более легкими", я думал о переносе этого "определения подверженности ошибкам" в один .c
исходный файл, который будет содержать только эту структуру. Этот файл будет "опасным быть осторожным" и включить его в мой фактический "нормальный функциональный" файл.
Что вы думаете об этом? Это допустимая ситуация для включения .c
исходного файла? Есть ли еще лучший способ обработки моей структуры?
Ответы
Ответ 1
Вы должны использовать назначенные инициализаторы, как показано в ответе Иана Аббота.
Кроме того, если индексы массива смежны, как представляется, здесь, вы можете использовать перечисление:
toto.h
typedef enum
{
TOTO_IND,
TITI_IND,
...
TATA_IND,
TOTO_N // this is not a data item but the number of items in the enum
} toto_t;
toto.c
const MyElements elems [] = {
[TITI_IND] = {"TITI", 27, "English"},
[TATA_IND] = {"TATA", 45, "Spanish"},
[TOTO_IND] = {"TOTO", 18, "French"},
};
И теперь вы можете проверить целостность данных массива в целом с помощью static assert:
_Static_assert(sizeof elems/sizeof *elems == TOTO_N,
"Mismatch between toto_t and elems is causing rain in Africa");
_Static_assert(sizeof elems/sizeof *elems == TOTO_N, ERR_MSG);
где ERR_MSG
определяется как
#define STR(x) STR2(x)
#define STR2(x) #x
#define ERR_MSG "Mismatching toto_t. Holding on line " STR(__LINE__)
Ответ 2
Вы можете использовать назначенные инициализаторы для инициализации элементов elems[]
не зная явного значения каждого идентификатора индекса (или макроса).
const MyElements elems[] = {
[TOTO_IND] = {"TOTO", 18, "French"},
[TITI_IND] = {"TITI", 27, "English"},
[TATA_IND] = {"TATA", 45, "Spanish"},
};
Элементы массива будут инициализированы одинаково, даже если вы измените порядок, который они отображают в исходном коде:
const MyElements elems[] = {
[TITI_IND] = {"TITI", 27, "English"},
[TATA_IND] = {"TATA", 45, "Spanish"},
[TOTO_IND] = {"TOTO", 18, "French"},
};
Если длина массива автоматически устанавливается из инициализатора, как указано выше (т. [NUM_ELEMS]
помощью []
а не [NUM_ELEMS]
), тогда длина будет больше, чем максимальный индекс элемента.
Это позволяет сохранить значения индекса и внешнее объявление массива elems
в файле.h и определить elems
массива elems
в отдельном файле.c.
Ответ 3
Другие ответы уже рассмотрели его более четко, но для полноты, вот подход x-macros, если вы готовы пойти по этому пути и рискнуть яростью вашего коллеги.
X-макросы - это форма генерации кода с использованием встроенного препроцессора C. Целью является сокращение повторения до минимума, хотя и с некоторыми недостатками:
- Исходный файл, который генерирует перечисления и структуры с использованием препроцессора, может показаться сложным, если вы не привыкли к ним.
- По сравнению с внешним скриптом сборки, который будет генерировать исходные файлы, с помощью x-макросов вы никогда не увидите, как создается сгенерированный код во время компиляции, если вы не используете параметр компилятора и не проверяете предварительно обработанный файл вручную.
- Поскольку вы не видите предварительно обработанный вывод, вы не можете использовать отладчик для выполнения сгенерированного кода так же, как с кодом, созданным внешним скриптом.
Вы начинаете с создания списка макрокоманд в отдельном файле, например elements.inc
, без определения того, что на самом деле делает макрос в данный момент:
// elements.inc
// each row passes a set of parameters to the macro,
// although at this point we haven't defined what the
// macro will output
XMACRO(TOTO, 18, French)
XMACRO(TITI, 27, English)
XMACRO(TATA, 45, Spanish)
И затем вы определяете макрос каждый раз, когда вам нужно включить этот список, чтобы каждый вызов отображался в одну строку конструкции, которую вы хотите создать, - и вы обычно повторяете это несколько раз подряд, т.е.
// concatenate id with "_IND" to create enums, ignore code and description
// (notice how you don't need to use all parameters each time)
// e.g. XMACRO(TOTO, 18, French) => TOTO_IND,
#define XMACRO(id, code, description) id ## _IND,
typedef enum
{
# include "elements.inc"
ELEMENTS_COUNT
}
Elements;
#undef XMACRO
// create struct entries
// e.g. XMACRO(TOTO, 18, French) => [TOTO_IND] = { "TOTO", 18, "French" },
#define XMACRO(id, code, description) [id ## _IND] = { #id, code, #description },
const MyElements elems[] = {
{
# include "elements.inc"
};
#undef XMACRO
Которая будет предварительно обработана чем-то вроде:
typedef enum
{
TOTO_IND,
TITI_IND,
TATA_IND,
ELEMENTS_COUNT
}
Elements;
const MyElements elems[] = {
{
[TOTO_IND] = { "TOTO", 18, "French" },
[TITI_IND] = { "TITI", 27, "English" },
[TATA_IND] = { "TATA", 45, "Spanish" },
};
Очевидно, что частое обслуживание списка становится проще, за счет того, что генерирующий код становится более запутанным.
Ответ 4
Определение const
как static
в нескольких файлах не является хорошей идеей, потому что она создает несколько экземпляров большой переменной MyElements
. Это увеличит объем памяти во встроенной системе. static
определитель необходимо удалить.
Вот предлагаемое решение:
в файле.h
#define TOTO_IND 0
#define TITI_IND 1
…
#define TATA_IND 50
#define MAX_ELEMS 51
extern const MyElements elems[MAX_ELEMS];
в файле.c
#include "file.h"
const MyElements elems [MAX_ELEMS] = {
{"TOTO", 18, "French"},
{"TITI", 27, "English"},
...,
{"TATA", 45, "Spanish"}
}
После изменения разместите #include "file.h"
в необходимых файлах.c.
Ответ 5
Чтобы решить конкретный вопрос об использовании #include
с .c
файлом (другие ответы предлагают лучшие варианты, особенно тот, который у Groo), как правило, нет необходимости.
Все в файле .c
может быть сделано внешне видимым и доступным, поэтому вы можете ссылаться на него через прототипы функций и #extern
. Так, например, вы можете ссылаться на свою таблицу с помощью #extern const MyElements elems [];
в вашем основном .c
файле.
В качестве альтернативы вы можете поместить определения в файл .h
и включить это. Это позволяет вам отделить код так, как вы хотите. Имейте в виду, что все #include
- это вставка содержимого включенного файла, в котором содержится оператор #include
, поэтому он не должен иметь какого-либо определенного расширения файла. .h
используется по соглашению, и большинство IDE автоматически добавят файлы .c
в список файлов, которые будут скомпилированы, но, насколько это касается компилятора, именование произвольно.