2016-01-07 11 views
5

Rozwijam oprogramowanie układowe dla aplikacji osadzonej z ograniczeniami pamięci. Mam zestaw poleceń, które muszą zostać przetworzone po ich otrzymaniu. Każde polecenie przypada na inne "wiadra", a każde "wiadro" dostaje zakres prawidłowych numerów poleceń. Stworzyłem dwa ENUMs, jak pokazano poniżej, aby to osiągnąć.Używanie ENUM jako map bitowych, jak sprawdzać poprawność w 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 

Gdy funkcja obsługi komend odbierze kod polecenia, musi sprawdzić, czy polecenie jest włączone przed jego przetworzeniem. Planuję użyć bitmapy do tego. Polecenia mogą być włączone lub wyłączone z przetwarzania w czasie wykonywania. Mogę użyć int dla każdej grupy (dając mi 32 polecenia na grupę, zdaję sobie sprawę, że 0xN00 do 0xN20 są poprawnymi kodami poleceń i że inne kody z tego zakresu są marnowane). Mimo że kody poleceń są marnowane, wybór projektu ma tę zaletę, że można łatwo powiedzieć grupie kodu polecenia podczas przeglądania surowych danych na konsoli.

Ponieważ wielu programistów może dodawać polecenia do enum cmd_codes (nawet nowe wiadra mogą być dodawane w razie potrzeby do enum cmd_buckets), chcę się upewnić, że liczba kodów poleceń w każdym segmencie nie przekracza 32 (bitmapa jest int). Chcę złapać to w czasie kompilacji, a nie w czasie wykonywania. Oprócz sprawdzania każdej wartości BUCKET_N_CMD_MAX, jak poniżej i wyrzucania błędu czasu kompilacji, czy istnieje lepsze rozwiązanie?

#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 

Proszę również zasugerować, czy istnieje bardziej elegancki sposób zaprojektowania tego.

Dzięki, doceniam twój czas i cierpliwość.

+0

Komentarze można wysyłać; jednak wydaje mi się, że pytanie jest bardziej trafne dla forum Stack Overflow _Code Review_. Czy ktokolwiek może to przenieść do recenzji kodu? –

+1

@PaulOgilvie Pytanie wydaje się lepiej pasować do przepełnienia stosu. Przegląd kodu wymaga pełnych, działających przykładów kodu. – Lundin

+1

Ten przykład nie ma żadnego sensu. Masz stałą wartość 'BUCKET_1 = 0x100', którą następnie przypisujesz' CMD_BUCKET_1_START = BUCKET_1'. Końcowe wyliczenia otrzymają wartości 0x101, 0x102, ... a "BUCKET_1_CMD_MAX" będzie 0x106. Ponieważ 0x106 jest zawsze większe niż 0x20, twój statyczny dowództwo zawsze będzie wyzwalać. Opublikuj działający przykład. – Lundin

Odpowiedz

3

Najpierw napraw błąd w kodzie. Jak wspomniano w komentarzach, masz stałą wartość BUCKET_1 = 0x100, którą następnie przypisujesz CMD_BUCKET_1_START = BUCKET_1. Wynikiem końcowym będą wartości 0x101, 0x102, ... i BUCKET_1_CMD_MAX będzie 0x106. Ponieważ 0x106 jest zawsze większe niż 0x20, twój statyczny dowództwo zawsze będzie wyzwalać.

naprawić tak, że rzeczywiście sprawdza całkowitą liczbę elementów w enum zamiast, jak to:

#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) 
... 

Przyjmując powyższe jest stała, a następnie można wymienić liczne kontrole z pojedynczym makro.Nie jest to wielki postęp, ale przynajmniej redukuje kodu powtórzenia:

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

Wyjście z gcc w przypadku jednej stałej jest zbyt duży:

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

EDIT

Jeśli łączenie błąd Napraw za pomocą makra kontrolnego, otrzymasz to:

#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

Przypuszczam, że można również połączyć poprawki błędów z makrem asercji, ale wtedy makro staje się bardziej niepoprawne. – Lundin

+0

Nie widzę powodu, aby wprowadzić makro pomocnika CHECK. Zmniejsza czytelność, nie dodając niczego. Ponadto, myślę, że rozwiązanie faktycznie zwiększa powtarzanie kodu, ponieważ teraz trzeba mieć zarówno dodatkową listę 'BUCKET_1_CMD_N' #defines, jak i listę wywołań makr' BUCKET_CHECK'. – kfx

+0

@kfx Nie ma "dodatkowej listy", niezbędna jest funkcjonalność, aby program działał. Ale jak napisałem w powyższym komentarzu, można go scalić z makrem BUCKET_CHECK. Jeśli chodzi o czytelność: jeśli uważasz, że 'BUCKET_ ## n ## _CMD_N <= BUCKET_MAX' jest bardziej czytelny, to za wszelką cenę używaj tego formularza ... Zapisywanie wszystkiego w jednym wierszu też działa ... – Lundin

1

Po pierwsze, polecenia preprocesora nie działają w ten sposób. Preprocesor C jest w stanie "zobaczyć" tylko nazwy instruowane przez instrukcję #define lub przekazuje je jako flagi kompilatora. Nie można wyświetlić stałych zdefiniowanych jako część parametru enum lub ze słowem kluczowym const. Powinieneś użyć _Static_assert do sprawdzenia poprawności poleceń zamiast preprocesora.

chodzi o komendy, chciałbym zaproponować, aby wszystkie polecenia numerowane w zakresie 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, 
}; 

Potem wystarczy tylko pojedynczą wartość ochronną, aby sprawdzić, czy wszystkie polecenia są w poprawnym zakresie:

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

Aby użyć polecenia-bitowe lub je razem z wiadra "offset":

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

Działa to, ale ma efekt uboczny, że stracisz 'cmd_codes' type i każdy sprawdzający go typ kompilatora (który może ale nie musi być problemem). – user694733

+0

Dziękuję za odpowiedź, używam '_Static_assert()' zgodnie z sugestią i przy użyciu podejścia @ Lundin – CCoder

Powiązane problemy