Генерирует предупреждение компилятора, если запятая инициализации массива const char * отсутствует

53

Я часто использую строковые литеральные таблицы в своем C-коде. Эти таблицы выглядят примерно так:

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

Проблема с приведенным выше кодом заключается в том, что если таблица становится длиннее и изменяется в процессе разработки, я время от времени забываю запятую. Код компилируется без проблем с пропущенной запятой, но моя программа завершается сбоем, поскольку последняя строка установлена ​​в NULL. Я использовал компиляторы MinGW и Keil для проверки.

Есть ли способ генерировать предупреждение компилятора для моей инициализации, если запятая отсутствует?

Джонни Шуберт
источник
1
Что произойдет, когда вы просто забудете добавить состояние в эту таблицу?
Jeroen3
1
@ Jeroen3 правда, это вызовет ту же ошибку. Использование статического подтверждения утверждения длины списка с помощью STATE_AMOUNT также решает эту проблему.
Джонни Шуберт

Ответы:

62

Обтекание каждой const char*пары в скобках должно решить проблему, как показано в следующем фрагменте:

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

Если вы забудете запятую, вы получите ошибку компиляции, похожую на: error: called object is not a function or function pointer

LIVE DEMO


Обратите внимание, что если вы забудете запятую, то на самом деле происходит то, что C фактически объединит две (или более) строки до следующей запятой или до конца массива. Например, допустим, вы забыли запятую, как показано ниже:

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

Вот что gcc-9.2генерирует (другие компиляторы генерируют похожий код):

.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

Понятно, что последние три строки объединены, и массив не соответствует ожидаемой длине.

Давиде Спатаро
источник
33

Вы можете позволить компилятору считать массив и генерировать сообщение об ошибке, если неожиданный результат:

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

Посмотрите эту ветку для идей для реализации, _Static_assertесли ваш компилятор очень старый и не поддерживает его.

В качестве бонуса это также может помочь, когда вы добавляете новые состояния, но забыли обновить таблицу строк. Но вы можете захотеть взглянуть и на X Macros.

М.М.
источник
Черт ... это был точный ответ, который я только собирался напечатать!
Сварщик
11

Я всегда использовал ссылку на явно размерный массив, чтобы решить эту проблему.

// 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
Статическое утверждение кажется гораздо более элегантным решением. Я полагаю, у вас есть привычка делать это до того, как статические утверждения были реализованы как часть языка? Вы все еще видите какое-либо преимущество этого по сравнению со статическим утверждением, которое проверяет ожидаемый размер массива?
Коди Грей
2
@CodyGray: Да, это было до-статическим утверждением теперь, когда вы упомянули об этом
Mooing Duck
9

Это не поможет компилятору помочь вам, но я нахожу, что его написание, как показано ниже, облегчает людям не ставить запятую:

static const char* const stateNames[STATE_AMOUNT] =
{
      "Init state"
    , "Run state"
    , "Pause state"
    , "Error state"
};
JonathanZ поддерживает MonicaC
источник
3
Добавить что-то в конце тоже проще. Вам не нужно редактировать предыдущую строку, чтобы добавить запятую. (Основная причина пропущенной запятой.)
datafiddler
@datafiddler: Согласен. Это также полезно для тонкой настройки списка столбцов в команде SQL SELECT, когда вы их комментируете и не комментируете. Вы часто хотите изменить последний; Вы редко хотите изменить первый. Таким образом, вам не нужно изменять несколько строк, чтобы закомментировать один элемент.
JonathanZ поддерживает MonicaC