2016-01-07 35 views
5

Sto sviluppando il firmware per un'applicazione incorporata con vincoli di memoria. Ho una serie di comandi che devono essere elaborati appena ricevuti. Ogni comando rientra in "bucket" diversi e ogni "bucket" ottiene un intervallo di numeri di comando validi. Ho creato due ENUM come mostrato di seguito per raggiungere questo obiettivo.Utilizzo di ENUM come bitmap, come convalidare in C

enum 
{ 
    BUCKET_1 = 0x100, // Range of 0x100 to 0x1FF 
    BUCKET_2 = 0x200, // Range of 0x200 to 0x2FF 
    BUCKET_3 = 0x300, // Range of 0x300 to 0x3FF 
    ... 
    ... 
    BUCKET_N = 0xN00 // Range of 0xN00 to 0xNFF 
} cmd_buckets; 

enum 
{ 
    //BUCKET_1 commands 
    CMD_BUCKET_1_START = BUCKET_1, 
    BUCKET_1_CMD_1, 
    BUCKET_1_CMD_2, 
    BUCKET_1_CMD_3, 
    BUCKET_1_CMD_4, 
    //Add new commands above this line 
    BUCKET_1_CMD_MAX, 

    //BUCKET_2 commands 
    CMD_BUCKET_2_START = BUCKET_2, 
    BUCKET_2_CMD_1, 
    BUCKET_2_CMD_2, 
    BUCKET_2_CMD_3, 
    //Add new commands above this line 
    BUCKET_2_CMD_MAX, 

    //BUCKET_3 commands 
    ... 
    ... 
    ... 

    //BUCKET_N commands 
    CMD_BUCKET_N_START = BUCKET_N 
    BUCKET_N_CMD_1, 
    BUCKET_N_CMD_2, 
    BUCKET_N_CMD_3, 
    BUCKET_N_CMD_4, 
    //Add new commands above this line 
    BUCKET_N_CMD_MAX, 
}cmd_codes 

Quando la mia funzione di gestore di comando riceve un codice di comando, si ha la necessità di controllare se il comando è abilitato prima di elaborarlo. Ho intenzione di usare una bitmap per questo. I comandi possono essere abilitati o disabilitati dall'elaborazione durante l'esecuzione. Posso usare un int per ogni gruppo (dandomi 32 comandi per gruppo, mi rendo conto che 0xN00 a 0xN20 sono codici di comando validi e che gli altri codici nell'intervallo sono sprecati). Anche se i codici dei comandi sono sprecati, la scelta di progettazione ha il vantaggio di indicare facilmente il gruppo del codice di comando durante la visualizzazione dei dati non elaborati su una console.

Poiché molti sviluppatori possono aggiungere comandi all'enum 'cmd_codes' (possono essere aggiunti anche nuovi bucket in base all'esigenza per l'enum 'cmd_buckets'), voglio assicurarmi che il numero di codici di comando in ciascun bucket non superi 32 (bitmap è int). Voglio prendere questo in fase di compilazione piuttosto che in fase di esecuzione. A parte il controllo di ogni valore di BUCKET_N_CMD_MAX come di seguito e l'invio di un errore di compilazione, esiste una soluzione migliore?

#if (BUCKET_1_CMD_MAX > 0x20) 
#error ("Number of commands in BUCKET_1 exceeded 32") 
#endif 

#if (BUCKET_2_CMD_MAX > 0x20) 
#error ("Number of commands in BUCKET_2 exceeded 32") 
#endif 

#if (BUCKET_3_CMD_MAX > 0x20) 
#error ("Number of commands in BUCKET_3 exceeded 32") 
#endif 
... 
... 
... 
#if (BUCKET_N_CMD_MAX > 0x20) 
#error ("Number of commands in BUCKET_N exceeded 32") 
#endif 

Si prega di suggerire inoltre se esiste un modo più elegante per progettare questo.

Grazie, apprezzo il tempo e la pazienza.

+0

I commenti possono essere fatti; tuttavia, penso che la tua domanda sia più adatta al forum _Code Review_ di Stack Overflow. Qualcuno può spostarlo al codice Review? –

+1

@PaulOgilvie La domanda sembra più adatta per Stack Overflow. La revisione del codice richiede esempi di codice completi e funzionanti. – Lundin

+1

Questo esempio non ha alcun senso. Si dispone di un costante 'BUCKET_1 = 0x100' che poi assegna' CMD_BUCKET_1_START = BUCKET_1'. Le enumerazioni finali saranno quindi ottenere i valori 0x101, 0x102, ... e 'BUCKET_1_CMD_MAX' saranno 0x106. Poiché 0x106 è sempre maggiore di 0x20, l'assert statico verrà sempre attivato. Si prega di inviare un esempio di lavoro. – Lundin

risposta

3

Per prima cosa correggere il bug nel codice. Come menzionato nei commenti, hai una costante BUCKET_1 = 0x100 che poi assegni a CMD_BUCKET_1_START = BUCKET_1. Le enumerazioni finali avranno quindi valori 0x101, 0x102, ... e BUCKET_1_CMD_MAX sarà 0x106. Poiché 0x106 è sempre maggiore di 0x20, l'assert statico verrà sempre attivato.

Fix che così che in realtà controlla il numero totale di elementi nella enum invece, in questo modo:

#define BUCKET_1_CMD_N (BUCKET_1_CMD_MAX - CMD_BUCKET_1_START) 
#define BUCKET_2_CMD_N (BUCKET_2_CMD_MAX - CMD_BUCKET_2_START) 
... 

Supponendo che quanto sopra è fissato, quindi è possibile sostituire i numerosi controlli con un singolo macro.Non un grande miglioramento, ma almeno riduce codice ripetizione:

#define BUCKET_MAX 32 // use a defined constant instead of a magic number 

// some helper macros:  
#define CHECK(n) BUCKET_ ## n ## _CMD_N 
#define STRINGIFY(n) #n 

// the actual macro: 
#define BUCKET_CHECK(n) \ 
    _Static_assert(CHECK(n) <= BUCKET_MAX, \ 
       "Number of commands in BUCKET_" STRINGIFY(n) "_CMD_N exceeds BUCKET_MAX."); 


// usage: 
int main (void) 
{ 
    BUCKET_CHECK(1); 
    BUCKET_CHECK(2); 
} 

uscita da gcc in caso una costante è troppo grande:

error: static assertion failed: "Number of commands in BUCKET_1_CMD_N exceeds BUCKET_MAX." 
note: in expansion of macro 'BUCKET_CHECK' 

EDIT

Se combinando l'errore fissare con la macro di controllo, si otterrebbe in questo modo:

#define BUCKET_MAX 32 

#define CHECK(n) (BUCKET_##n##_CMD_MAX - CMD_BUCKET_##n##_START) 
#define STRINGIFY(n) #n 
#define BUCKET_CHECK(n) \ 
    _Static_assert(CHECK(n) <= BUCKET_MAX, \ 
       "Number of commands in BUCKET " STRINGIFY(n) " exceeds BUCKET_MAX."); 

int main (void) 
{ 
    BUCKET_CHECK(1); 
    BUCKET_CHECK(2); 
} 
+0

Suppongo che potresti anche unire la correzione di bug con la macro dell'asserzione, ma poi la macro diventa più confusa. – Lundin

+0

Non vedo il motivo dell'introduzione della macro helper CHECK. Riduce la leggibilità non aggiungendo nulla. Inoltre, penso che la soluzione in realtà aumenta la ripetizione del codice, perché ora bisogna avere sia l'ulteriore elenco di '# definisce BUCKET_1_CMD_N' e la lista dei' BUCKET_CHECK' invocazioni macro. – kfx

+0

@kfx Non esiste una "lista aggiuntiva", è necessaria la funzionalità per far funzionare il programma. Ma come ho scritto nel commento sopra, potrebbe essere unito alla macro BUCKET_CHECK. Per quanto riguarda la leggibilità: se ritieni che 'BUCKET_ ## n ## _CMD_N <= BUCKET_MAX' sia più leggibile, allora usa sempre quel modulo ... Anche scrivere tutto su una singola riga ... – Lundin

1

Prima di tutto, i comandi del preprocessore non funzionano in questo modo. Il preprocessore C è in grado di "vedere" solo i nomi indicati dall'istruzione #define o passa come flag del compilatore. Non è in grado di vedere le costanti definite come parte di un enum o con la parola chiave const. È necessario utilizzare _Static_assert per convalidare i comandi anziché il preprocessore.

Per quanto riguarda i comandi, vorrei suggerire di avere tutti i comandi numerati nella gamma 0..0x20:

enum { 
    BUCKET_1_CMD_1, 
    BUCKET_1_CMD_2, 
    ... 
    BUCKET_1_CMD_MAX, 
}; 
enum { 
    BUCKET_2_CMD_1, 
    BUCKET_2_CMD_2, 
    ... 
    BUCKET_2_CMD_MAX, 
}; 

allora avete bisogno di un solo valore di guardia per verificare se tutti i comandi sono in gamma valida:

#define MAX_COMMAND 0x20 
_Static_assert(BUCKET_1_CMD_MAX <= MAX_COMMAND, "too many bucket 1 commands"); 
_Static_assert(BUCKET_2_CMD_MAX <= MAX_COMMAND, "too many bucket 2 commands"); 

Per utilizzare i comandi, bit per bit, o insieme con il secchio "compensati":

enum { 
BUCKET_1 = 0x100, 
BUCKET_2 = 0x200, 
}; 
... 
int cmd = BUCKET_2 | BUCKET_2_CMD_1; 
+0

Questo funziona, ma ha un effetto collaterale che si perde 'tipo cmd_codes', e qualsiasi tipo compilatore controllo con esso (che può o non può essere un problema). – user694733

+0

Grazie per la tua risposta, sto usando '_Static_assert()' come suggerito e usando l'approccio di @ Lundin – CCoder