Générer un avertissement du compilateur si la virgule d'initialisation du const const * array est manquante

53

J'utilise beaucoup de tables littérales de chaînes dans mon code C. Ces tableaux ressemblent tous plus ou moins à ceci:

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

Le problème avec le code ci-dessus est que si la table s'allonge et est modifiée pendant le développement, j'oublie de temps en temps une virgule. Le code se compile sans problème avec une virgule manquante, mais mon programme finit par se bloquer lorsque la dernière chaîne est définie sur NULL. J'ai utilisé les compilateurs MinGW et Keil pour vérifier.

Existe-t-il un moyen de générer un avertissement du compilateur pour mon initialisation si la virgule est manquante?

Jonny Schubert
la source
1
Que se passe-t-il lorsque vous oubliez simplement d'ajouter un état à ce tableau?
Jeroen3
1
@ Jeroen3 true, cela provoquerait la même erreur. L'utilisation d'une assertion statique testant la longueur de la liste par rapport à STATE_AMOUNT résout également ce problème.
Jonny Schubert

Réponses:

62

Envelopper chaque const char*parenthèse dans une paire devrait résoudre le problème, comme illustré dans l'extrait de code suivant:

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

Si vous oubliez une virgule, vous obtiendrez une erreur de compilation similaire à: error: called object is not a function or function pointer

DÉMO EN DIRECT


Notez que si vous oubliez la virgule, ce qui se passe réellement est que C concatène réellement les deux (ou plus) chaînes jusqu'à la prochaine virgule, ou la fin du tableau. Par exemple, supposons que vous oubliez la virgule comme indiqué ci-dessous:

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

C'est ce qui gcc-9.2génère (d'autres compilateurs génèrent du code similaire):

.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

Il est clair que les trois dernières chaînes sont concaténées et que le tableau n'a pas la longueur attendue.

Davide Spataro
la source
33

Vous pouvez laisser le compilateur compter le tableau et générer un message d'erreur en cas de résultat inattendu:

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

Voir ce fil pour des idées à implémenter _Static_assertsi votre compilateur est très ancien et ne le supporte pas.

En prime, cela peut également vous aider lorsque vous ajoutez de nouveaux états mais oubliez de mettre à jour la table des chaînes. Mais vous voudrez peut-être également examiner les macros X.

MM
la source
Merde .... c'était la réponse exacte que j'allais juste taper!
The Welder
11

J'ai toujours utilisé une référence à un tableau de taille explicite pour résoudre ce problème.

// 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;
Mooing Duck
la source
4
Une assertion statique semble être une solution beaucoup plus élégante. Je suppose que vous avez pris l'habitude de le faire avant que les assertions statiques ne soient implémentées dans le langage? En voyez-vous encore maintenant un avantage par rapport à une assertion statique qui vérifie la taille attendue du tableau?
Cody Gray
2
@CodyGray: Oui, c'était une affirmation pré-statique maintenant que vous en parlez
Mooing Duck
9

Cela n'apporte pas le compilateur pour vous aider, mais je trouve que l'écrire comme ci-dessous permet aux humains de ne pas laisser de virgule plus facile:

static const char* const stateNames[STATE_AMOUNT] =
{
      "Init state"
    , "Run state"
    , "Pause state"
    , "Error state"
};
JonathanZ soutient MonicaC
la source
3
Ajouter quelque chose à la fin est également plus facile. Vous n'avez pas à modifier la ligne précédente pour ajouter une virgule. (La principale raison de la virgule manquante.)
Datafiddler
@datafiddler: D'accord. Il est également utile pour affiner la liste des colonnes dans une commande SQL SELECT, lorsque vous les commentez et les commentez. Vous voulez souvent changer le dernier; vous voulez rarement changer le premier. De cette façon, vous n'avez pas à modifier plusieurs lignes pour mettre en commentaire un élément.
JonathanZ prend en charge MonicaC le