Створіть попередження компілятора, якщо відсутня кома ініціалізації масиву const char * array


53

Я багато використовую рядкові буквальні таблиці в своєму коді С. Усі ці таблиці виглядають приблизно так:

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 Макроси.


Чорт .... це була точна відповідь, яку я просто збирався набрати!
The Welder

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

3
Додавати щось наприкінці теж простіше. Не потрібно редагувати попередній рядок, щоб додати кому. (Основна причина відсутньої коми.)
datafiddler

@datafiddler: Погоджено. Це також корисно для тонкої настройки списку стовпців команди SQL SELECT, коли ви коментуєте та скасовуєте їх. Вам часто хочеться змінити останній; ти рідко хочеш змінити перший. Таким чином, вам не потрібно змінювати кілька рядків, щоб коментувати один елемент.
JonathanZ підтримує MonicaC
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.