Можно ли включать #include исходный файл .c для удобства сопровождения встроенного кода C? - PullRequest
0 голосов
/ 11 октября 2018

Я не опытный программист 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 исходного файла?Есть ли другой лучший способ обработки моей структуры?

Ответы [ 5 ]

0 голосов
/ 12 октября 2018

Чтобы ответить на конкретный вопрос об использовании #include с файлом .c (другие ответы предлагают лучшие варианты, особенно тот, который предлагает Groo), обычно нет необходимости.

Все в *Файл 1005 * можно сделать внешне видимым и доступным, так что вы можете ссылаться на него через прототипы функций и #extern.Так, например, вы можете ссылаться на вашу таблицу с помощью #extern const MyElements elems []; в вашем основном .c файле.

В качестве альтернативы, вы можете поместить определения в файл .h и включить его.Это позволяет вам разделять код так, как вы хотите.Имейте в виду, что все, что делает #include, это вставляет содержимое включенного файла, где находится оператор #include, поэтому у него не должно быть какого-либо конкретного расширения файла..h используется в соответствии с соглашением, и большинство IDE автоматически добавляют файлы .c в список файлов, подлежащих компиляции, но что касается компилятора, именование является произвольным.

0 голосов
/ 11 октября 2018

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

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

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

Вы начинаете ссоздание списка макросов вызовов в отдельном файле, например 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)

И затем вы каждый раз определяете макросВы должны включить этот список, чтобы каждый вызов отображался в одной строке конструктора.t вы хотите создать - и вы обычно повторяете это несколько раз подряд, то есть

// 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" },
};

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

0 голосов
/ 11 октября 2018

Вы можете использовать назначенные инициализаторы для инициализации элементов 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]), тогдадлина будет на единицу больше, чем максимальный индекс элемента.

Это позволяет сохранить значения индекса и внешнюю декларацию массива elems в файле .h и определить содержимое массива elemsв отдельном файле .c.

0 голосов
/ 11 октября 2018

Вы должны использовать назначенные инициализаторы, как показано в ответе Иана Эббота.

Кроме того, если индексы массива смежны, как это имеет место здесь, вы можете использовать перечисление вместо:

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(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__)
0 голосов
/ 11 октября 2018

Определение const как static в нескольких файлах не является хорошей идеей, поскольку оно создает несколько экземпляров большой переменной MyElements.Это увеличит память во встроенной системе.Квалификатор static необходимо удалить.

Вот предлагаемое решение:

в file.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.

...