Gere aviso do compilador se estiver faltando vírgula de inicialização de matriz const char *

53

Estou usando muito tabelas literais de string no meu código C. Todas essas tabelas são mais ou menos assim:

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

O problema com o código acima é que, se a tabela ficar mais longa e for modificada durante o desenvolvimento, esqueço uma vírgula de tempos em tempos. O código é compilado sem problemas com uma vírgula ausente, mas meu programa acaba tendo um erro quando a última string é definida como NULL. Eu usei os compiladores MinGW e Keil para verificar.

Existe alguma maneira de gerar um aviso de compilador para minha inicialização se a vírgula estiver ausente?

Jonny Schubert
fonte
11
O que acontece quando você simplesmente esquece de adicionar um estado a esta tabela?
Jeroen3 27/01
11
@ Jeroen3 true Isso causaria o mesmo erro. O uso de uma declaração estática testando o comprimento da lista em relação a STATE_AMOUNT também resolve esse problema.
Jonny Schubert

Respostas:

62

O agrupamento de todos os const char*parênteses deve resolver o problema, conforme mostrado no seguinte snippet:

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

Se você esquecer uma vírgula, receberá um erro de compilação semelhante a: error: called object is not a function or function pointer

DEMONSTRAÇÃO AO VIVO


Observe que, se você esquecer a vírgula, o que realmente acontece é que C concatenará as duas (ou mais) seqüências de caracteres até a próxima vírgula ou o final da matriz. Por exemplo, digamos que você esqueça a vírgula, como mostrado a seguir:

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

É isso que gcc-9.2gera (outros compiladores geram código semelhante):

.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

É claro que as três últimas cadeias de caracteres são concatenadas e a matriz não tem o comprimento que você esperaria.

Davide Spataro
fonte
33

Você pode deixar o compilador contar a matriz e gerar uma mensagem de erro se resultar 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" );

Veja este tópico para idéias para implementar _Static_assertse o seu compilador for muito antigo e não for compatível.

Como bônus, isso também pode ajudar quando você adiciona novos estados, mas esquece de atualizar a tabela de cadeias. Mas você também pode querer procurar nas X Macros.

MILÍMETROS
fonte
Droga ... essa era a resposta exata que eu ia digitar!
The Welder
11

Eu sempre usei uma referência a uma matriz de tamanho explícito para resolver isso.

// 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
fonte
4
Uma afirmação estática parece ser uma solução muito mais elegante. Suponho que você tenha o hábito de fazer isso antes de as asserções estáticas serem implementadas como parte da linguagem. Você ainda vê alguma vantagem disso em relação a uma asserção estática que verifica o tamanho esperado da matriz?
Cody Gray
2
@CodyGray: Sim, isso foi pré-estático-afirmativo agora que você mencionou
Mooing Duck
9

Isso não traz o compilador para ajudá-lo, mas acho que escrevê-lo como abaixo facilita para os humanos não soltarem uma vírgula:

static const char* const stateNames[STATE_AMOUNT] =
{
      "Init state"
    , "Run state"
    , "Pause state"
    , "Error state"
};
JonathanZ suporta MonicaC
fonte
3
Anexar algo no final também é mais fácil. Você não precisa editar a linha anterior para adicionar uma vírgula. (O principal motivo da vírgula ausente.)
datafiddler 28/01
@datafiddler: concordou. Também é útil para ajustar a lista de colunas em um comando SQL SELECT, quando você as está comentando e não comentando. Você frequentemente deseja alterar o último; você raramente quer mudar o primeiro. Dessa forma, você não precisa modificar várias linhas para comentar um item.
JonathanZ apoia MonicaC