Bagaimana cara memastikan enum dan array memiliki entri yang sama saat kompilasi?

Ini untuk aplikasi tertanam yang berjalan pada level rendah di UC. Bagian lain dari sistem memerlukan pengaturan parameter dan UC lokal perlu memelihara daftar parameter. Setiap parameter terdiri dari ID 8 bit dan nilai 8 bit. ID dimulai pada 0x70 karena keterbatasan memori di ujung yang lain.

Untuk menjaga penggunaan memori serendah mungkin, saya telah mengimplementasikan penyimpanan parameter sebagai array yang berisi ID dan Nilai untuk semua parameter yang dapat ditulis. Kemudian ada daftar parameter ini yang disebutkan di file header untuk memungkinkan bagian lain dari aplikasi mengakses parameter tersebut.

Apakah ada cara untuk memastikan bahwa daftar enum dan array parameter memiliki entri yang sama dalam urutan yang sama? Saya telah mendokumentasikan kodenya dengan cukup teliti (tidak semua disertakan dalam ekstrak) tetapi saya ingin melakukan beberapa bentuk pemeriksaan pada waktu kompilasi untuk memastikan bahwa daftar dan array cocok.

Hal lain yang saya tidak yakin adalah apakah ini cara paling efisien untuk mengimplementasikannya. Saya harus dapat mengulangi parameter untuk mengirimkannya ke bagian lain dari sistem dan saya perlu menggunakan memori sesedikit mungkin.

Dari 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[];

Dari 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 sumber


Jawaban (3)


Anda berada di jalur yang benar dengan Param_NUMBER_OF_IMPLEMENTED_PARAMS. Sayangnya Anda tidak dapat menggunakan ini sebagai ukuran array, karena itu hanya menjamin bahwa array tidak memiliki inisialisasi lebih banyak daripada enum. Itu tidak melindungi terhadap array yang memiliki inisialisasi terlalu sedikit.

Cara untuk memastikan hal ini adalah dengan membuat pernyataan statis tentang ukuran enum versus ukuran array. Pertahankan deklarasi array sebagai PARAMETER parameters[] lalu lakukan

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

Namun kata kunci standar _Static_assert hanya tersedia di C11, dan makro standar static_assert hanya tersedia di C11 dan C++. Pada kompiler lama, Anda harus membuatnya sendiri. Misalnya:

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

Ini akan memberikan kesalahan kompiler "tidak dapat mendeklarasikan array dengan ukuran nol" yang samar jika penegasan gagal.


Pengurutan dapat dipastikan dengan menggunakan inisialisasi yang ditunjuk untuk item array:

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

Inisialisasi yang ditunjuk tidak mencegah duplikat, tetapi jika ada entri duplikat, hanya entri terakhir yang akan digunakan (dan Anda biasanya mendapat peringatan kompiler). Entri duplikat tersebut tidak akan mempengaruhi ukuran array.

person Lundin    schedule 09.12.2016
comment
Sepertinya saya tidak bisa membuat makro pernyataan statis berfungsi dengan kompiler saya (berasal dari gcc 4.1) tetapi memiliki inisialisasi yang ditunjuk adalah ide bagus yang tidak saya sadari. Saya telah menambahkannya dan saya hanya akan memastikan bahwa pengembang lain di proyek ini mengetahui apa yang telah saya lakukan. Saya mungkin akan menjadi orang yang melakukan pemeliharaan kode ini di masa mendatang. - person RobbG; 09.12.2016
comment
@RobbG Penegasan statis diperkenalkan dengan C11, yang kemudian diperkenalkan di sekitar gcc 4.8. Anda seharusnya dapat menggunakan makro pernyataan statis buatan saya pada kompiler C mana pun. - person Lundin; 09.12.2016

Anda dapat menggunakan Param_NUMBER_OF_IMPLEMENTED_PARAMS sebagai ukuran array. Setidaknya itu akan menyebabkan kompiler bereaksi jika Anda memiliki banyak elemen dalam daftar penginisialisasi array.

Namun tidak ada cara (standar dan portabel) untuk memperingatkan bahwa Anda tidak menginisialisasi semua elemen. Semua elemen yang tidak Anda inisialisasi akan diinisialisasi "nol".

Jelas tidak ada cara untuk memastikan ketertiban, tidak pada waktu kompilasi.

Mungkin ada alat penganalisis statis yang mungkin bisa membantu Anda. Dan tentu saja peninjauan kode yang ketat.

person Some programmer dude    schedule 09.12.2016
comment
Terima kasih, saya terutama mencari apakah ada cara untuk melakukan itu. Saya pikir dengan dokumentasinya seharusnya sudah cukup jelas apa yang terjadi dan apa yang perlu ditambahkan jika kita perlu mengubah daftarnya di masa depan. Saya akan melihat alat analisis statis di masa depan sehingga saya dapat menambahkannya ke daftar hal-hal yang saya cari di dalamnya. - person RobbG; 09.12.2016
comment
Tentu saja Anda dapat memastikan hal ini pada waktu kompilasi, dengan pernyataan statis. Ini adalah skenario yang umum dan solusinya telah ada selama beberapa dekade sebelum pernyataan statis dimasukkan dalam standar. - person Lundin; 09.12.2016
comment
@RobbG Lihat jawaban saya tentang cara memastikan ini secara otomatis dalam standar C. Tidak perlu tinjauan manual atau penganalisis statis. - person Lundin; 09.12.2016
comment
@Lundin Sial... Saya sedang memikirkan tentang pernyataan statis, tetapi tidak tahu itu ada untuk C11. Sayangnya kompiler untuk lingkungan tertanam cenderung tertinggal cukup banyak sehingga penyelesaian masalah statis mungkin harus digunakan. - person Some programmer dude; 09.12.2016
comment
@RobbG Static menegaskan seperti yang disarankan dan ditunjukkan oleh Lundin memang menawarkan solusi untuk keduanya masalah: ukuran dan pemesanan. Satu pernyataan statis untuk memastikan ukuran, dan satu untuk setiap elemen untuk memastikan pemesanan. - person Some programmer dude; 09.12.2016
comment
@Someprogrammerdude Kompiler yang tertanam tampaknya akhirnya menyusul, mereka mulai mendukung fitur dasar C11. Dan tentu saja GCC memiliki C11 selama bertahun-tahun. Sedangkan untuk pemesanannya, lihat update di jawaban saya. - person Lundin; 09.12.2016
comment
@Lundin Seandainya saya bisa memberi suara positif kepada Anda dua kali. :) Saya rasa sudah waktunya saya kembali ke C untuk belajar yang menyenangkan. :) - person Some programmer dude; 09.12.2016
comment
@someprogrammerdude Itulah salah satu hal yang saya sukai tentang pemrograman tertanam, c adalah bahasa yang luar biasa namun tak kenal ampun dengan banyak keunikan dan fitur menarik untuk membuat Anda tetap waspada. Sangat baik untuk belajar bersama. - person RobbG; 09.12.2016

Anda dapat menggunakan makro generator, seperti ini.
Saat Anda memodifikasi PARAM_BLOCK, enum dan array akan dibuat secara otomatis.

#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)
};

Tapi mungkin Anda bisa menyederhanakan array parameter Anda, karena Anda memiliki array yang diurutkan berdasarkan id, Anda tidak memerlukan id sama sekali.

#define GENERATE_WRITE_PARAM_ARRAY(paramId,paramValue)   paramValue,
uint8 writeable_parameters[] = {
        GENERATE_WRITE_PARAM_ARRAY(PARAM_BLOCK)
    };
person jeb    schedule 09.12.2016
comment
Saya sengaja menghindari solusi ini, karena apa yang disebut makro X menghasilkan kode yang sangat jelek. Seharusnya tidak ada kebutuhan untuk menggunakannya dalam kasus ini. Solusi makro X selalu menjadi pilihan terakhir dan harus dihindari jika memungkinkan. - person Lundin; 09.12.2016