Genere una advertencia del compilador si falta la coma de inicialización de matriz const char *

53

Estoy usando tablas de cadenas literales mucho en mi código C. Todas estas tablas se parecen más o menos a esto:

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

El problema con el código anterior es que si la tabla se alarga y se modifica durante el desarrollo, de vez en cuando olvido una coma. El código se compila sin problemas con una coma faltante, pero mi programa termina fallando cuando se configura la última cadena NULL. Usé los compiladores MinGW y Keil para verificar.

¿Hay alguna forma de generar una advertencia de compilación para mi inicialización si falta la coma?

Jonny Schubert
fuente
1
¿Qué sucede cuando simplemente olvidas agregar un estado a esta tabla?
Jeroen3
1
@ Jeroen3 cierto, esto causaría el mismo error. El uso de una aserción estática que prueba la longitud de la lista contra STATE_AMOUNT también resuelve este problema.
Jonny Schubert

Respuestas:

62

Envolver cada uno const char*en un par de paréntesis debería resolver el problema como se muestra en el siguiente fragmento:

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

Si olvida una coma, obtendrá un error de compilación similar a: error: called object is not a function or function pointer

DEMO EN VIVO


Tenga en cuenta que si olvida la coma, lo que realmente sucede es que C realmente concatenará las dos (o más) cadenas hasta la próxima coma, o al final de la matriz. Por ejemplo, supongamos que olvida la coma como se muestra a continuación:

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

Esto es lo que gcc-9.2genera (otros compiladores generan código similar):

.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

Está claro que las últimas tres cadenas están concatenadas y la matriz no tiene la longitud que cabría esperar.

Davide Spataro
fuente
33

Puede dejar que el compilador cuente la matriz y genere un mensaje de error si se produce un resultado inesperado:

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

Vea este hilo para ideas para implementar_Static_assert si su compilador es muy antiguo y no lo admite.

Como beneficio adicional, esto también puede ayudar cuando agrega nuevos estados pero se olvida de actualizar la tabla de cadenas. Pero es posible que también desee estudiar X Macros.

MM
fuente
Maldición ... ¡esta era la respuesta exacta que iba a escribir!
The Welder
11

Siempre he usado una referencia a una matriz de tamaño explícito para resolver esto.

// 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;
Pato mugido
fuente
44
Una afirmación estática parece una solución mucho más elegante. ¿Supongo que tienes la costumbre de hacer esto antes de que se implementaran aserciones estáticas como parte del lenguaje? ¿Todavía ve alguna ventaja de esto sobre una aserción estática que verifica el tamaño esperado de la matriz?
Cody Gray
2
@CodyGray: Sí, esto fue una afirmación preestática ahora que lo mencionas
Mooing Duck
9

Esto no trae el compilador para ayudarlo, pero creo que escribirlo a continuación hace que sea más fácil para los humanos no soltar una coma:

static const char* const stateNames[STATE_AMOUNT] =
{
      "Init state"
    , "Run state"
    , "Pause state"
    , "Error state"
};
JonathanZ apoya a MonicaC
fuente
3
Agregar algo al final también es más fácil. No tiene que editar la línea anterior para agregar una coma. (La razón principal de la coma que falta.)
datafiddler
@datafiddler: De acuerdo. También es útil para ajustar la lista de columnas en un comando SQL SELECT, cuando las comenta y descomenta. A menudo quieres cambiar el último; rara vez quieres cambiar el primero. De esta manera, no tiene que modificar varias líneas para comentar un elemento.
JonathanZ apoya a MonicaC el