Как убедиться, что перечисление и массив имеют одинаковые записи при компиляции?

Это для встроенного приложения, которое работает на низком уровне на UC. Другая часть системы требует установки параметров, а локальный УК должен поддерживать список параметров. Каждый параметр состоит из 8-битного идентификатора и 8-битного значения. Идентификаторы начинаются с 0x70 из-за ограничений памяти на другом конце.

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

Есть ли способ убедиться, что список перечислений и массив параметров имеют одни и те же записи в одном и том же порядке? Я довольно тщательно задокументировал код (не все включено в выдержки), но я хотел бы добавить некоторую форму проверки во время компиляции, чтобы убедиться, что список и массив совпадают.

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

От 1_:

/*******************************************************************************/
/* IDs for all parameters must be defined                                      */
/* Defaults only need to be defined for rut-time writable parameters           */
/* All parameters must have an ID define                                       */
/* Writable parameters must also have:                                         */
/*    * DefaultValue define                                                    */
/*    * Entry in ParamIndex                                                    */
/*    * Initialesed default entry in Parameters (contained in C file)          */
/*    * If min and max values are not 0x00 and 0xFF then define those too      */
/*******************************************************************************/

// Parameter IDs - All parameters need this defining
#define Param_ActualTemp_ID                         0xE0
#define Param_OperationMode_ID                      0xE1
#define Param_MaintenanceModePID0_ID                0xE5
#define Param_MaintenanceModePID1_ID                0xE6
#define Param_FirmwareVersionL_ID                   0xEB
#define Param_FirmwareVersionH_ID                   0xEC
#define Param_SerialNumberL_ID                      0xED
#define Param_SerialNumberH_ID                      0xEE
#define Param_SerialNumberHH_ID                     0xEF
#define Param_MaxTemperature_ID                     0xFC
#define Param_NULL_ID                               0xFF

// Parameter Default Values - All writable parameters need this defining
#define Param_NULL_DefaultValue                     0xFF
#define Param_OperationMode_DefaultValue            0
#define Param_MaintenanceModePID0_DefaultValue      0xFF
#define Param_MaintenanceModePID1_DefaultValue      0xFF
#define Param_MaxTemperature_DefaultValue           0x54

typedef struct
{
  const uint8    id;
        uint8    value;
} PARAMETER;

// Parameter Index, any writable parameters need an entry here
// Used as array index for parameters[], do not edit existing values
typedef enum
{
  Param_NULL = 0,
  Param_OperationMode,
  Param_MaintenanceModePID0,
  Param_MaintenanceModePID1,
  Param_MaxTemperature,

    /* Additional values must be entered above this line */
  Param_NUMBER_OF_IMPLEMENTED_PARAMS
} ParamIndex;

extern PARAMETER parameters[];

От parameters.c:

PARAMETER parameters[] = {
  { .id = Param_NULL_ID,                 .value = Param_NULL_DefaultValue},
  { .id = Param_OperationMode_ID,        .value = Param_OperationMode_DefaultValue},
  { .id = Param_MaintenanceModePID0_ID,  .value = Param_MaintenanceModePID0_DefaultValue},
  { .id = Param_MaintenanceModePID1_ID,  .value = Param_MaintenanceModePID1_DefaultValue},
  { .id = Param_MaxTemperature_ID,       .value = Param_MaxTemperature_DefaultValue}
};

person RobbG    schedule 09.12.2016    source источник


Ответы (3)


Вы на правильном пути с Param_NUMBER_OF_IMPLEMENTED_PARAMS. К сожалению, вы не можете использовать это как размер массива, потому что это гарантирует только то, что массив не имеет больше инициализаторов, чем перечисление. Он не защищает от того, что в массиве слишком мало инициализаторов.

Способ обеспечить это — сделать статическое утверждение размера перечисления по сравнению с размером массива. Сохраните объявление массива как PARAMETER parameters[], а затем выполните

_Static_assert(Param_NUMBER_OF_IMPLEMENTED_PARAMS == sizeof(parameters)/sizeof(*parameters), 
               "Array initializer list does not match enum");

Однако стандартное ключевое слово _Static_assert доступно только в C11, а стандартный макрос static_assert доступен только в C11 и C++. На старых компиляторах вам придется изобретать это самостоятельно. Например:

#define STATIC_ASSERT(expr) {typedef uint8_t S_ASSERT[(expr) ? 1 : 0];}

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


Упорядочивание можно обеспечить с помощью назначенных инициализаторов для элементов массива:

PARAMETER parameters[] = {
  [Param_NULL_DefaultValue] = { .id = Param_NULL_ID, .value = Param_NULL_DefaultValue},
  ...

Назначенные инициализаторы не предотвращают дублирование, но в случае дублирования записей будет использоваться только последний (и обычно вы получаете предупреждение компилятора). Такие повторяющиеся записи не повлияют на размер массива.

person Lundin    schedule 09.12.2016
comment
Кажется, я не могу заставить макрос статического утверждения работать с моим компилятором (производным от gcc 4.1), но наличие назначенных инициализаторов - отличная идея, о которой я не знал. Я добавил это, и я просто удостоверюсь, что другой разработчик в этом проекте знает о том, что я сделал. В любом случае, я, вероятно, буду тем, кто будет заниматься обслуживанием этого кода в будущем. - person RobbG; 09.12.2016
comment
@RobbG Static assert был представлен с C11, который, в свою очередь, был представлен где-то в gcc 4.8. Однако вы должны иметь возможность использовать мой самодельный макрос static assert на любом компиляторе C. - person Lundin; 09.12.2016

Вы можете использовать Param_NUMBER_OF_IMPLEMENTED_PARAMS в качестве размера массива. Это, по крайней мере, приведет к тому, что компилятор отреагирует, если у вас будет много элементов в списке инициализаторов массива.

Однако нет (стандартного и портативного) способа предупредить, что вы не инициализируете все элементы. Все элементы, которые вы не инициализируете, будут инициализированы "нулем".

Нет определенно способа обеспечить порядок, во всяком случае, во время компиляции.

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

person Some programmer dude    schedule 09.12.2016
comment
Спасибо, я в основном искал, есть ли встроенный способ сделать это. Я думаю, что с документацией должно быть достаточно ясно, что происходит и что нужно добавить, если нам нужно будет изменить список в будущем. В будущем я рассмотрю инструменты статического анализа, поэтому я мог бы добавить это в список вещей, которые я ищу в них. - person RobbG; 09.12.2016
comment
Конечно, вы можете убедиться в этом во время компиляции с помощью статических утверждений. Это хорошо известный сценарий, и обходные пути существовали десятилетиями, прежде чем статические утверждения были включены в стандарт. - person Lundin; 09.12.2016
comment
@RobbG См. мой ответ о том, как обеспечить это автоматически в стандартном C. Нет необходимости в ручном просмотре или статических анализаторах. - person Lundin; 09.12.2016
comment
@Lundin Черт ... Я думал о статическом утверждении, но не знал, что они существуют для C11. К сожалению, компиляторы для встроенных сред, как правило, сильно отстают, поэтому, вероятно, придется использовать обходной путь static assert. - person Some programmer dude; 09.12.2016
comment
@RobbG Static утверждает, как предложено и показано Лундином, действительно предлагает решение обеих проблем: размер и порядок. Одно статическое утверждение для обеспечения размера и по одному для каждого элемента для обеспечения порядка. - person Some programmer dude; 09.12.2016
comment
@Someprogrammerdude Встроенные компиляторы, кажется, наконец-то догоняют, они начинают поддерживать основные функции C11. И, конечно, у GCC уже много лет есть C11. Что касается заказа, смотрите обновление в моем ответе. - person Lundin; 09.12.2016
comment
@Lundin Хотел бы я дважды проголосовать за тебя. :) Я думаю, пришло время вернуться к C, чтобы немного поучиться. :) - person Some programmer dude; 09.12.2016
comment
@someprogrammerdude Это одна из вещей, которые мне нравятся во встроенном программировании: c — замечательный, хотя и неумолимый язык с множеством причуд и интересных функций, которые держат вас в напряжении. Отлично подходит для обучения с. - person RobbG; 09.12.2016

Вы можете использовать макросы генератора, подобные этим.
Когда вы изменяете PARAM_BLOCK, перечисление и массив создаются автоматически.

#define PARAM_BLOCK(GEN_FUNC)  \
  GEN_FUNC(Param_NULL_ID, Param_NULL_DefaultValue)         \
  GEN_FUNC(Param_OperationMode_ID, Param_OperationMode_DefaultValue) \
  GEN_FUNC(Param_MaintenanceModePID0_ID, Param_MaintenanceModePID0_DefaultValue) \
  GEN_FUNC(Param_MaintenanceModePID1_ID, Param_MaintenanceModePID1_DefaultValue) \
  GEN_FUNC(Param_MaxTemperature_ID, Param_MaxTemperature_DefaultValue) \

#define GENERATE_PARAM_ARRAY(paramId,paramValue)   { .id = paramId, .value = paramValue },
#define GENERATE_PARAM_ENUM(paramId,paramValue)          paramId,

typedef enum
{
  GENERATE_PARAM_ENUM(PARAM_BLOCK)
    /* Additional values must be entered above this line */
  Param_NUMBER_OF_IMPLEMENTED_PARAMS
} ParamIndex;

PARAMETER parameters[] = {
    GENERATE_PARAM_ARRAY(PARAM_BLOCK)
};

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

#define GENERATE_WRITE_PARAM_ARRAY(paramId,paramValue)   paramValue,
uint8 writeable_parameters[] = {
        GENERATE_WRITE_PARAM_ARRAY(PARAM_BLOCK)
    };
person jeb    schedule 09.12.2016
comment
Я намеренно избегал этого решения, так как так называемые X-макросы производят ужасно уродливый код. В этом случае не должно быть необходимости их использовать. Решения макроса X всегда являются самым последним средством, и их следует избегать, если это возможно. - person Lundin; 09.12.2016