จะแน่ใจได้อย่างไรว่า enum และ array มีรายการเดียวกันเมื่อคอมไพล์

นี่เป็นสำหรับแอปพลิเคชันแบบฝังที่ทำงานในระดับต่ำบน uC อีกส่วนหนึ่งของระบบจำเป็นต้องมีการตั้งค่าพารามิเตอร์ และ uC ภายในเครื่องจำเป็นต้องรักษารายการพารามิเตอร์ไว้ แต่ละพารามิเตอร์ประกอบด้วย ID 8 บิตและค่า 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}
};

person RobbG    schedule 09.12.2016    source แหล่งที่มา


คำตอบ (3)


คุณมาถูกทางแล้วด้วย Param_NUMBER_OF_IMPLEMENTED_PARAMS น่าเสียดายที่คุณไม่สามารถใช้สิ่งนี้เป็นขนาดอาเรย์ได้ เพราะนั่นรับประกันได้ว่าอาเรย์จะไม่มีตัวเริ่มต้นมากกว่า enum มันไม่ได้ป้องกันอาเรย์ที่มีตัวเริ่มต้นน้อยเกินไป

วิธีที่จะแน่ใจได้ว่านี่คือการยืนยันขนาดแจงนับแบบคงที่เทียบกับขนาดอาร์เรย์ เก็บการประกาศอาร์เรย์เป็น 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 คุณควรจะสามารถใช้มาโครยืนยันคงที่แบบโฮมบรูนของฉันกับคอมไพเลอร์ 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 น่าเสียดายที่คอมไพเลอร์สำหรับสภาพแวดล้อมแบบฝังตัวมักจะล้าหลังค่อนข้างมาก ดังนั้นจึงอาจต้องใช้การยืนยันวิธีแก้ปัญหาแบบคงที่ - person Some programmer dude; 09.12.2016
comment
@RobbG Static ยืนยันตามที่แนะนำ และแสดงโดย Lundin เสนอวิธีแก้ปัญหาสำหรับปัญหา ทั้งสอง: ขนาดและการสั่ง. การยืนยันแบบคงที่หนึ่งรายการเพื่อให้แน่ใจว่ามีขนาด และอีกหนึ่งรายการสำหรับแต่ละองค์ประกอบเพื่อให้แน่ใจว่ามีการสั่งซื้อ - 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 enum และอาร์เรย์จะถูกสร้างขึ้นโดยอัตโนมัติ

#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)
    };
person jeb    schedule 09.12.2016
comment
ฉันหลีกเลี่ยงวิธีแก้ปัญหานี้โดยตั้งใจ เนื่องจากสิ่งที่เรียกว่ามาโคร X จะสร้างโค้ดที่น่าเกลียดอย่างยิ่ง ไม่จำเป็นที่จะต้องใช้มันในกรณีนี้ โซลูชันแมโคร X เป็นทางเลือกสุดท้ายเสมอ และควรหลีกเลี่ยงหากเป็นไปได้ - person Lundin; 09.12.2016