Генерирует предупреждение компилятора, если запятая инициализации массива const char * отсутствует - PullRequest
53 голосов
/ 27 января 2020

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

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

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

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

Ответы [ 4 ]

62 голосов
/ 27 января 2020

Завершение каждого 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 голосов
/ 27 января 2020

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

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 голосов
/ 27 января 2020

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

// 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;
9 голосов
/ 27 января 2020

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

static const char* const stateNames[STATE_AMOUNT] =
{
      "Init state"
    , "Run state"
    , "Pause state"
    , "Error state"
};
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...