switchステートメントがブレークを必要とするように設計されたのはなぜですか?
-
05-07-2019 - |
質問
単純なswitchステートメントを指定
switch (int)
{
case 1 :
{
printf("1\n");
break;
}
case 2 :
{
printf("2\n");
}
case 3 :
{
printf("3\n");
}
}
ケース2にbreakステートメントがないことは、ケース3のコード内で実行が継続されることを意味します。 これは偶然ではありません。そのように設計されました。なぜこの決定が下されたのですか?これにより、ブロックに自動ブレークセマンティクスを使用する場合と比べて、どのようなメリットがありますか?根拠は何ですか?
解決
多くの回答は、 break
ステートメントを要求するための理由として機能することに焦点を当てているようです。
Cが設計されたとき、これらのコンストラクトがどのように使用されるかという経験がほとんどなかったため、これは単に間違いだったと思います。
ピーターヴァンデルリンデンは、彼の著書「エキスパートCプログラミング」で主張しています:
Sun Cコンパイラのソースを分析しました デフォルトの落下頻度を確認する スルーが使用されました。サンANSI C コンパイラフロントエンドには244スイッチがあります 各ステートメントには 7つのケースの平均。フォールスルー これらすべてのケースのわずか3%で発生します。
つまり、通常のスイッチ 動作は97%の間違っているです。 コンパイラだけではありません- 逆に、フォールスルーが使用された場所 この分析では、しばしば より頻繁に発生する状況 他のソフトウェアよりもコンパイラで、 たとえば、演算子をコンパイルするとき 1つまたは2つを持つことができます オペランド:
switch (operator->num_of_operands) { case 2: process_operand( operator->operand_2); /* FALLTHRU */ case 1: process_operand( operator->operand_1); break; }
ケースフォールスルーは非常に広い 欠陥として認識されている 特別なコメント規約でさえも 上記のように、lint"これは 実際にこれらのケースの3%の1つ フォールスルーが必要でした。"
C#では、各ケースブロックの最後に明示的なジャンプステートメントを必要とするのは良い考えだと思います(ただし、ステートメントのブロックが1つだけである限り、複数のケースラベルをスタックできます)。 C#では、1つのケースを別のケースにフォールスルーさせることができます- goto
を使用して次のケースにジャンプすることにより、フォールスルーを明示的に行う必要があります。
JavaがCのセマンティクスを破る機会を取らなかったのは残念です。
他のヒント
多くの点で、cは標準のアセンブリイディオムへのクリーンなインターフェイスにすぎません。ジャンプテーブル駆動のフロー制御を記述する場合、プログラマは「制御構造」から抜け出すか飛び出すかを選択でき、ジャンプアウトには明示的な指示が必要です。
つまり、cは同じことをします...
Duffのデバイスを実装するには、明らかに:
dsend(to, from, count)
char *to, *from;
int count;
{
int n = (count + 7) / 8;
switch (count % 8) {
case 0: do { *to = *from++;
case 7: *to = *from++;
case 6: *to = *from++;
case 5: *to = *from++;
case 4: *to = *from++;
case 3: *to = *from++;
case 2: *to = *from++;
case 1: *to = *from++;
} while (--n > 0);
}
}
ケースが暗黙的に中断するように設計されている場合、フォールスルーすることはできません。
case 0:
case 1:
case 2:
// all do the same thing.
break;
case 3:
case 4:
// do something different.
break;
default:
// something else entirely.
すべての場合にスイッチが暗黙的にブレークアウトするように設計されている場合、選択の余地はありません。スイッチケース構造は、より柔軟になるように設計されました。
switchステートメントのcaseステートメントは、単なるラベルです。
値を切り替えると、switchステートメントは基本的に、一致する値を持つラベルに対して goto を実行します。
これは、次のラベルの下のコードにパススルーしないようにするためにブレークが必要であることを意味します。
この方法で実装された理由はなぜです-いくつかのシナリオではswitchステートメントのフォールスルーの性質が役立つ場合があります。例:
case optionA:
// optionA needs to do its own thing, and also B's thing.
// Fall-through to optionB afterwards.
// Its behaviour is a superset of B's.
case optionB:
// optionB needs to do its own thing
// Its behaviour is a subset of A's.
break;
case optionC:
// optionC is quite independent so it does its own thing.
break;
次のようなことを許可するには:
switch(foo) {
case 1:
/* stuff for case 1 only */
if (0) {
case 2:
/* stuff for case 2 only */
}
/* stuff for cases 1 and 2 */
case 3:
/* stuff for cases 1, 2, and 3 */
}
case
キーワードを goto
ラベルと考えると、もっと自然になります。
複数のケースで同じコード(または同じコードを順番に)実行する必要がある場合、コードの重複を排除します。
アセンブリ言語レベルでは、各ケース間でブレークするかどうかは関係ないため、フォールスルーケースのオーバーヘッドはとにかくゼロになります。
ベクトル内の値を構造体に割り当てる場合に偶然に遭遇しました:データベクトルが構造体内のデータメンバーの数よりも短い場合、残りのメンバーがデフォルト値のままになります。その場合、 break
を省略することは非常に役立ちました。
switch (nShorts)
{
case 4: frame.leadV1 = shortArray[3];
case 3: frame.leadIII = shortArray[2];
case 2: frame.leadII = shortArray[1];
case 1: frame.leadI = shortArray[0]; break;
default: TS_ASSERT(false);
}
ここで多くが指定しているように、単一のコードブロックが複数のケースで機能することを許可します。この は、「ケースごとのコードブロック」よりもswitch文でよく発生します。例で指定します。
フォールスルーのないケースごとのコードブロックがある場合は、if-elseif-elseブロックの使用を検討する必要があります。これはより適切と思われるためです。