Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to ensure enum and array have same entries at compile?

This is for an embedded application that runs at a low level on a uC. Another part of the system requires parameters to be set and the local uC needs to maintain a list of parameters. Each parameter consists of an 8 bit ID and an 8 bit value. The ID's begin at 0x70 due to memory limitations at the other end.

In order to keep the memory usage as low as possible I have implemented the parameter store as an array containing the IDs and Values for all the writable parameters. There is then an enumerated list of these parameters in the header file to allow other parts of the application to access the parameters.

Is there any way to ensure that the enum list and the array of parameters have the same entries in the same order? I have documented the code fairly thoroughly (not all included in the extracts) but I would like to put in some form of check at compile time to ensure that the list and array match up.

Another thing I'm not sure of is if this is the most efficient way of implementing this. I need to be able to iterate through the parameters in order to transmit them to the other part of the system and I need to use as little memory as possible.

From parameters.h:

/*******************************************************************************/
/* 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[];

From 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}
};
like image 996
RobbG Avatar asked Dec 09 '16 11:12

RobbG


1 Answers

You are on the right track with the Param_NUMBER_OF_IMPLEMENTED_PARAMS. Unfortunately you cannot use this as an array size, because that only guarantees that the array doesn't have more initializers than the enum. It does not protect against the array having too few initializers.

The way to ensure this is to make a static assertion of the enum size versus the array size. Keep the array declaration as PARAMETER parameters[] and then do

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

The standard keyword _Static_assert is however only available in C11, and the standard macro static_assert is only available in C11 and C++. On older compilers, you will have to invent it yourself. For example:

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

This will give a cryptic "cannot declare an array with size zero" compiler error if the assert fails.


Ordering can be ensured by using designated initializers for the array items:

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

Designated initializers does not prevent duplicates, but in case of duplicate entries only the last one will be used (and you typically get a compiler warning). Such duplicate entries will not affect the array size.

like image 63
Lundin Avatar answered Nov 15 '22 04:11

Lundin