【问题标题】:How to ensure enum and array have same entries at compile?如何确保枚举和数组在编译时具有相同的条目?
【发布时间】:2017-04-24 21:36:41
【问题描述】:

这适用于在 uC 上以低级别运行的嵌入式应用程序。系统的另一部分需要设置参数,本地 uC 需要维护一个参数列表。每个参数由一个 8 位 ID 和一个 8 位值组成。由于另一端的内存限制,ID 从 0x70 开始。

为了尽可能降低内存使用率,我将参数存储实现为一个数组,其中包含所有可写参数的 ID 和值。然后在头文件中有一个这些参数的枚举列表,以允许应用程序的其他部分访问这些参数。

有什么方法可以确保枚举列表和参数数组具有相同顺序的相同条目?我已经相当彻底地记录了代码(并非全部包含在摘录中),但我想在编译时进行某种形式的检查,以确保列表和数组匹配。

我不确定的另一件事是,这是否是最有效的实现方式。我需要能够遍历参数以便将它们传输到系统的其他部分,并且我需要使用尽可能少的内存。

来自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[];

来自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}
};

【问题讨论】:

    标签: c arrays enums embedded


    【解决方案1】:

    您可以使用Param_NUMBER_OF_IMPLEMENTED_PARAMS 作为数组大小。如果数组初始值设定项列表中有许多元素,这至少会导致编译器做出反应。

    但是,没有(标准和可移植的)方法来警告您没有初始化所有元素。您未初始化的所有元素都将被“零”初始化。

    绝对没有办法确保顺序,而不是在编译时。

    可能有静态分析工具可以帮助您。当然还有严格的代码审查。

    【讨论】:

    • 谢谢,我主要是在寻找是否有内置的方法来做到这一点。我认为通过文档应该相当清楚发生了什么以及如果我们将来需要更改列表需要添加什么。我将来会研究静态分析工具,因此我可能会将其添加到我在其中查找的内容列表中。
    • 当然,您可以在编译时使用静态断言来确保这一点。这是一个众所周知的场景,在静态断言被包含在标准中之前,变通方法已经存在了几十年。
    • @RobbG 请参阅我的答案,了解如何在标准 C 中自动确保这一点。无需手动审查或静态分析器。
    • @Lundin Damn... 我在考虑静态断言,但不知道它们存在于 C11 中。不幸的是,嵌入式环境的编译器往往落后很多,因此可能必须使用“解决方法”静态断言。
    • @RobbG Static asserts 正如 Lundin 建议的 and shown 所提供的,确实为 both 问题提供了解决方案:尺寸和排序。一个静态断言以确保大小,每个元素一个以确保排序。
    【解决方案2】:

    您与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},
      ...
    

    指定的初始值设定项不会防止重复,但在重复条目的情况下,只会使用最后一个(并且您通常会收到编译器警告)。此类重复条目不会影响数组大小。

    【讨论】:

    • 我似乎无法让静态断言宏与我的编译器(源自 gcc 4.1)一起使用,但拥有指定的初始化程序是一个我不知道的好主意。我已经添加了它,我会确保这个项目的其他开发人员知道我做了什么。无论如何,我将来可能会成为维护此代码的人。
    • @RobbG 静态断言是随 C11 引入的,而后者又是在 gcc 4.8 左右引入的。不过,您应该可以在任何 C 编译器上使用我自制的静态断言宏。
    【解决方案3】:

    您可以使用生成器宏,例如这些。
    修改 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)
    };
    

    但也许你可以简化你的参数数组,因为你有一个按 id 排序的数组,你根本不需要 id。

    #define GENERATE_WRITE_PARAM_ARRAY(paramId,paramValue)   paramValue,
    uint8 writeable_parameters[] = {
            GENERATE_WRITE_PARAM_ARRAY(PARAM_BLOCK)
        };
    

    【讨论】:

    • 我故意避免使用这个解决方案,因为所谓的“X 宏”会产生非常难看的代码。在这种情况下应该不需要使用它们。 “X 宏”解决方案始终是最后的手段,应尽可能避免。
    猜你喜欢
    • 2019-12-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-18
    • 1970-01-01
    相关资源
    最近更新 更多