Подтвердить что ты не робот

С++ Принудительная компиляция времени/предупреждение при неявном провале в коммутаторе

Операторы

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

switch(val) {
    case 0:
        foo();
        break;
    case 1:
        bar();
        // oops
    case 2:
        baz();
        break;
    default:
        roomba();
}

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

Я уверен, что ответ на этот вопрос нет, но: есть ли какой-либо способ (или предлагается в будущем), чтобы он мог попросить компилятор выбросить ошибку (или хотя бы предупреждение!), если ваш case не имеет хотя бы одного из break; или чего-то вроде // fallthru? Было бы неплохо иметь опцию защитного программирования для использования операторов switch.

4b9b3361

Ответ 1

У Well clang есть -Wimplicit-fallthrough, о котором я не знал, но нашел, используя -Weverything. Поэтому для этого кода он дает мне следующее предупреждение (видеть его в прямом эфире):

warning: unannotated fall-through between switch labels [-Wimplicit-fallthrough]
case 2:
^
note: insert '[[clang::fallthrough]];' to silence this warning
case 2:
^
[[clang::fallthrough]]; 
note: insert 'break;' to avoid fall-through
case 2:
^
break; 

Единственная документация, которую я могу найти для этого флага, находится в Reference Attribute Reference, в котором говорится:

Атрибут clang:: fallthrough используется вместе с -Wimplicit-fallthrough аргумент для аннотации преднамеренного провала между метками коммутатора. Он может применяться только к нулевому утверждению помещается в точке исполнения между любым выражением и следующим переключатель метки. Обычно отмечать эти места конкретным комментарий, но этот атрибут предназначен для замены комментариев более строгая аннотация, которая может быть проверена компилятором.

и дает пример того, как отмечать явный провал:

case 44:  // warning: unannotated fall-through
g();
[[clang::fallthrough]];
case 55:  // no warning

Это использование attribute, чтобы отметить явный провал, имеет недостаток в том, что он не переносимый. Visual Studio генерирует ошибку, а gcc генерирует следующее предупреждение:

warning: attributes at the beginning of statement are ignored [-Wattributes]

что является проблемой, если вы хотите использовать -Werror.

Я пробовал это с помощью gcc 4.9, и похоже, что gcc не поддерживает это предупреждение:

ошибка: непризнанная опция командной строки '-Wimplicit-fallthrough'

От поддерживается GCC 7, -Wimplicit-fallthrough и __attribute__((fallthrough)) может использоваться для подавления предупреждений, когда провал преднамерен. В некоторых сценариях GCC распознает "провальные" комментарии, но его можно легко смутить .

Я не вижу способа генерировать такое предупреждение для Visual Studio.

Примечание, Chandler Carruth объясняет, что -Weverything не используется для производства:

Это безумная группа, которая буквально разрешает каждое предупреждение в Clang. Не используйте это в своем коде. Он предназначен исключительно для Clang разработчиков или для изучения того, какие существуют предупреждения.

но полезно выяснить, какие предупреждения существуют.

Ответ 2

Я всегда пишу break; перед каждым case следующим образом:

switch(val) {
    break; case 0:
        foo();
    break; case 1:
        bar();
    break; case 2:
        baz();
    break; default:
        roomba();
}

Таким образом, это намного очевиднее, если отсутствует break;. Первоначальный break; избыточен, я полагаю, но он помогает быть последовательным.

Это обычный оператор switch, я просто использовал пробелы по-другому, удалив новую строку, которая обычно после break; и до следующего case.

Ответ 3

Совет. Если вы последовательно ставите пустую строку между предложениями case, отсутствие "перерыва" становится более заметным для человека, снижающего код:

switch (val) {
    case 0:
        foo();
        break;

    case 1:
        bar();

    case 2:
        baz();
        break;

    default:
        roomba();
}

Это не так эффективно, когда в отдельных случаях case содержится много кода, но это, как правило, плохой запах кода.

Ответ 4

Вот ответ на навязчивую ненависть.

Во-первых, операторы switch представляют собой причудливые gotos. Они могут быть объединены с другим потоком управления (классно, Duff Device), но очевидная аналогия здесь - это переход или два. Здесь бесполезный пример:

switch (var) {
    CASE1: case 1:
        if (foo) goto END; //same as break
        goto CASE2; //same as fallthrough
    CASE2: case 2:
        break;
    CASE3: case 3:
        goto CASE2; //fall *up*
    CASE4: case 4:
        return; //no break, but also no fallthrough!
    DEFAULT: default:
        continue; //similar, if you're in a loop
}
END:

Рекомендую ли я это? Нет. На самом деле, если вы рассматриваете это только для того, чтобы комментировать провал, тогда ваша проблема на самом деле является чем-то другим.

Этот код очень ясно показывает, что провал может произойти в случае 1, но, как показывают другие биты, это очень мощный метод в целом, который также поддается злоупотреблению. Будьте осторожны, если вы его используете.

Забыть break? Ну, тогда вы также будете иногда забывать любую аннотацию, которую вы выбираете. Забыть об учетной записи при смене оператора switch? Ты плохо программист. При модификации операторов switch (или действительно любого кода) вам необходимо сначала их понять.


Честно говоря, я очень редко делаю такую ​​ошибку (забыв о перерыве) - конечно, меньше, чем я сделал другие "общие" ошибки программирования (например, строгий псевдоним). Чтобы быть в безопасности, я в настоящее время (и рекомендую вам сделать) просто напишу //fallthrough, так как это по крайней мере проясняет намерение.

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