web-dev-qa-db-ja.com

C ++スイッチの暗黙的なフォールスルーでコンパイル時エラー/警告を強制する

switchステートメントは非常に便利ですが、プログラマーがbreakステートメントを忘れるという一般的なバグにつながります。

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

フォールスルーが明示的に望まれる場合があるため、明らかに警告は表示されません。良いコーディングスタイルは、フォールスルーが意図的である場合にコメントすることを提案しますが、それでは不十分な場合があります。

私はこの質問に対する答えがノーであることを確信していますが、:caseには少なくともbreak;または// fallthruswitchステートメントを使用するための防御的なプログラミングオプションがあると便利です。

53
Barry

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

このフラグについて見つけることができる唯一のドキュメントは 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開発者向け、または存在する警告の調査専用です。

ただし、どの警告が存在するかを把握するのに役立ちます。

C++ 17の変更

C++ 17では、属性[[fallthrough]]を取得します [dcl.attr.fallthrough] p1

属性トークンのフォールスルーは、nullステートメントに適用できます(9.2)。このようなステートメントはフォールスルーステートメントです。属性トークンのフォールスルーは、各属性リストに最大で1回出現し、attributeargument-句は存在しないものとします。フォールスルーステートメントは、囲むswitchステートメント内にのみ表示されます(9.4.2)。フォールスルーステートメントの後に実行される次のステートメントは、ラベルが同じswitchステートメントのcaseラベルまたはデフォルトラベルであるラベル付きステートメントです。そのような声明がない場合、プログラムは不正な形式です。

...

_[ Example:
void f(int n) {
void g(), h(), i();
switch (n) {
  case 1:
  case 2:
    g();
    [[fallthrough]];
  case 3: // warning on fallthrough discouraged
    h();
  case 4: // implementation may warn on fallthrough
    i();
    [[fallthrough]]; // ill-formed
  }
}
—end example ]
_

属性を使用したライブの例 を参照してください。

67
Shafik Yaghmour

次のように、各caseの前に常にbreak;を記述します。

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

このように、break;が欠落している場合、目に見えるのははるかに明白です。最初のbreak;は冗長であると思いますが、一貫性を保つのに役立ちます。

これは従来のswitchステートメントです。通常、break;の後、次のcaseの前にある改行を削除して、異なる方法で空白を使用しました。

12
Aaron McDaid

アドバイス:ケース節の間に一貫して空白行を入れると、コードをざっと読んでいる人には「ブレーク」がないことがわかりやすくなります。

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

    case 1:
        bar();

    case 2:
        baz();
        break;

    default:
        roomba();
}

これは、個々のcase句内に多くのコードがある場合はそれほど効果的ではありませんが、それ自体が悪臭を放つ傾向があります。

6
zwol

強迫的な憎しみに対する答えは次のとおりです。

まず、switchステートメントは派手なgotoです。これらは他の制御フローと組み合わせることもできます(有名なのは Duff's Device )が、ここでの明らかな類推はgotoまたは2です。役に立たない例です:

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:

これをお勧めしますか?実際、フォールスルーに注釈を付けるためだけにこれを検討している場合、問題は実際には別の問題です。

この種のコードはdoesそれを作るveryフォールスルーケース1で発生する可能性がありますが、他のビットが示すように、これは一般的に非常に強力な手法であり、悪用にも役立ちます。使用する場合は注意してください。

breakを忘れましたか?それで、あなたが選んだ注釈を忘れることもあるでしょう。 switchステートメントを変更するときにフォールスルーを考慮するのを忘れていますか?あなたは悪いプログラマーです。 switchステートメントを変更する場合(または実際にはanyコード)、最初にそれらを理解する必要があります。


正直なところ、私はこの種のエラー(ブレークを忘れる)はめったにありません。他の「一般的な」プログラミングエラー(厳密なエイリアスなど)を犯したよりも確かに少ないです。安全のために、私は現在//fallthrough、これは少なくとも意図を明確にするからです。

それ以外は、プログラマーが受け入れる必要がある現実です。コードを記述した後、コードを校正し、デバッグで時々起こる問題を見つけます。それが人生。

0
imallett