Genera avviso del compilatore se manca la virgola di inizializzazione const char * array


53

Sto usando molto le tabelle letterali di stringa nel mio codice C. Queste tabelle sembrano tutte più o meno così:

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

Il problema con il codice sopra è se la tabella si allunga e viene modificata durante lo sviluppo, di tanto in tanto dimentico una virgola. Il codice viene compilato senza problemi con una virgola mancante, ma il mio programma termina in modo anomalo quando viene impostata l'ultima stringa NULL. Ho usato i compilatori MinGW e Keil per verificare.

C'è un modo per generare un avviso del compilatore per la mia inizializzazione se manca la virgola?


1
Cosa succede quando si dimentica semplicemente di aggiungere uno stato a questa tabella?
Jeroen3,

1
@ Jeroen3 true questo causerebbe lo stesso errore. L'utilizzo di un'asserzione statica che verifica la lunghezza dell'elenco rispetto a STATE_AMOUNT risolve anche questo problema.
Jonny Schubert,

Risposte:


62

Il confezionamento const char*di una coppia tra parentesi dovrebbe risolvere il problema, come mostrato nel frammento seguente:

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

Se dimentichi una virgola, otterrai un errore di compilazione simile a: error: called object is not a function or function pointer

DIMOSTRAZIONE DAL VIVO


Si noti che se si dimentica la virgola, ciò che accade effettivamente è che C concatenerà effettivamente le due (o più) stringhe fino alla virgola successiva o alla fine dell'array. Ad esempio diciamo che hai dimenticato la virgola come mostrato di seguito:

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

Questo è ciò che gcc-9.2genera (altri compilatori generano codice simile):

.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

È chiaro che le ultime tre stringhe sono concatenate e l'array non è la lunghezza che ci si aspetterebbe.


33

È possibile consentire al compilatore di contare l'array e generare un messaggio di errore in caso di risultati imprevisti:

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

Vedi questo thread per idee da implementare _Static_assertse il tuo compilatore è molto vecchio e non lo supporta.

Come bonus, questo può aiutare anche quando aggiungi nuovi stati ma dimentichi di aggiornare la tabella delle stringhe. Ma potresti voler esaminare anche X Macro.


Accidenti .... questa era la risposta esatta che stavo per scrivere!
The Welder,

11

Ho sempre usato un riferimento a un array di dimensioni esplicite per risolvere questo problema.

// 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;

4
Un'asserzione statica sembra una soluzione molto più elegante. Suppongo che tu abbia l'abitudine di farlo prima che le asserzioni statiche fossero implementate come parte del linguaggio? Vedete ancora qualche vantaggio di questo rispetto a un'asserzione statica che verifica la dimensione prevista dell'array?
Cody Gray

2
@CodyGray: Sì, questa era un'affermazione pre-statica ora che me ne hai parlato
Mooing Duck

9

Questo non porta il compilatore ad aiutarti, ma trovo che scriverlo come sotto sia più facile per gli umani non rilasciare una virgola:

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

3
Anche aggiungere qualcosa alla fine è più facile. Non è necessario modificare la riga precedente per aggiungere una virgola. (Il motivo principale per la virgola mancante.)
datafiddler

@datafiddler: concordato. È anche utile per mettere a punto l'elenco delle colonne in un comando SQL SELECT, quando le stai inserendo e non commentando. Spesso vuoi cambiare l'ultimo; raramente vuoi cambiare il primo. In questo modo non è necessario modificare più righe per commentare un elemento.
JonathanZ supporta MonicaC il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.