Wygeneruj ostrzeżenie kompilatora, jeśli brakuje przecinka inicjalizacji const char *

53

Często używam tablic literałów łańcuchowych w moim kodzie C. Te tabele wyglądają mniej więcej tak:

static const char* const stateNames[STATE_AMOUNT] =
{
    "Init state",
    "Run state",
    "Pause state",
    "Error state",
};

Problem z powyższym kodem polega na tym, że jeśli tabela się wydłuża i jest modyfikowana podczas programowania, od czasu do czasu zapominam przecinek. Kod kompiluje się bez problemu z brakującym przecinkiem, ale mój program kończy się awarią, gdy ostatni ciąg jest ustawiony na NULL. Do weryfikacji użyłem kompilatorów MinGW i Keil.

Czy istnieje sposób wygenerowania ostrzeżenia kompilatora dla mojej inicjalizacji, jeśli brakuje przecinka?

Jonny Schubert
źródło
1
Co się stanie, gdy po prostu zapomnisz dodać stan do tej tabeli?
Jeroen3
1
@ Jeroen3 true spowoduje to ten sam błąd. Zastosowanie statycznego potwierdzenia, sprawdzanie długości listy względem STATE_AMOUNT, rozwiązuje również ten problem.
Jonny Schubert

Odpowiedzi:

62

Zawijanie co const char*parę nawiasów powinno rozwiązać problem, jak pokazano w poniższym fragmencie:

static const char* const stateNames[5] =
{
    ("Init state"),
    ("Run state"),
    ("Pause state")     //comma missing
    ("Pause state3"),
    ("Error state")
};

Jeśli zapomnisz przecinek, pojawi się błąd kompilacji podobny do: error: called object is not a function or function pointer

DEMO NA ŻYWO


Zauważ, że jeśli zapomnisz przecinek, tak naprawdę dzieje się tak, że C faktycznie połączy dwa (lub więcej) ciągi znaków do następnego przecinka lub końca tablicy. Powiedzmy na przykład, że zapomniałeś przecinka, jak pokazano poniżej:

static const char* const stateNames[] =
{
    "Init state",
    "Run state",
    "Pause state" //comma missing
    "Pause state3" //comma missing
    "Error state"
};

int main(void)
{  
    printf("%s\n", stateNames[0]);
    return 0;    
}

Oto, co gcc-9.2generuje (inne kompilatory generują podobny kod):

.LC0:
        .string "Init state"
        .string "Run state"
        .string "Pause statePause state3Error state" ; oooops look what happened
        .quad   .LC0
        .quad   .LC1
        .quad   .LC2
main:
        push    rbp
        mov     rbp, rsp
        mov     eax, OFFSET FLAT:.LC0
        mov     rdi, rax
        call    puts
        mov     eax, 0
        pop     rbp
        ret

Oczywiste jest, że ostatnie trzy ciągi są połączone, a tablica nie jest długością, jakiej można by oczekiwać.

Davide Spataro
źródło
33

Możesz pozwolić kompilatorowi zliczyć tablicę i wygenerować komunikat o błędzie, jeśli nieoczekiwany wynik:

enum { STATE_AMOUNT = 4 };

static const char* const stateNames[] =
{
    "Init state",
    "Run state",
    "Pause state"    // <--- missing comma
    "Error state",
};

_Static_assert( sizeof stateNames / sizeof *stateNames == STATE_AMOUNT,
        "oops, missed a comma" );

W tym wątku znajdziesz pomysły na wdrożenie, _Static_assertjeśli Twój kompilator jest bardzo stary i nie obsługuje go.

Jako bonus, może to również pomóc przy dodawaniu nowych stanów, ale zapomnij zaktualizować tabelę ciągów. Ale możesz także przyjrzeć się Makrom X.

MM
źródło
Cholera ... to była dokładna odpowiedź, którą zamierzałam wpisać!
Spawacz
11

Zawsze użyłem odwołania do tablicy o wyraźnie określonym rozmiarze, aby to rozwiązać.

// no explicit size here
static const char* const stateNames[] =
{
    "Init state",
    "Run state",
    "Pause state",
    "Error state",
};
static const char* const (&stateNameVerifier)[STATE_AMOUNT] = stateNames;

http://coliru.stacked-crooked.com/a/593fc2eac80782a6

main.cpp:10:32: error: reference to type 'const char *const [5]' could not bind to an lvalue of type 'const char *const [4]'
static const char* const (&stateNameVerifier)[STATE_AMOUNT] = stateNames;
Mucząca Kaczka
źródło
4
Twierdzenie statyczne wydaje się znacznie bardziej eleganckim rozwiązaniem. Przypuszczam, że masz w zwyczaju robić to, zanim statyczne twierdzenia zostały zaimplementowane jako część języka? Czy nadal widzisz tę przewagę nad twierdzeniem statycznym, które weryfikuje oczekiwany rozmiar tablicy?
Cody Gray
2
@CodyGray: Tak, to było pre-statyczne stwierdzenie teraz, kiedy o tym wspominasz
Mooing Duck
9

Nie pomaga to kompilatorowi, ale pisanie go tak jak poniżej ułatwia ludziom nie pozostawianie przecinka:

static const char* const stateNames[STATE_AMOUNT] =
{
      "Init state"
    , "Run state"
    , "Pause state"
    , "Error state"
};
JonathanZ obsługuje MonicaC
źródło
3
Dołączanie czegoś na końcu jest również łatwiejsze. Nie musisz edytować poprzedniego wiersza, aby dodać przecinek. (Główny powód brakującego przecinka).
datafiddler
@datafiddler: Uzgodniony. Jest także przydatny do dostrajania listy kolumn w poleceniu SQL SELECT, kiedy je komentujesz i nie komentujesz. Często chcesz zmienić ostatni; rzadko chcesz zmienić pierwszy. W ten sposób nie musisz modyfikować wielu linii, aby skomentować jeden element.
JonathanZ wspiera MonicaC