なぜswitch文は中断を​​必要とするように設計されたのですか?


139

単純なswitchステートメントがあるとします

switch (int)
{
    case 1 :
    {
        printf("1\n");
        break;
    }

    case 2 : 
    {
        printf("2\n");
    }

    case 3 : 
    {
        printf("3\n");
    }
}

ケース2にbreakステートメントがない場合、ケース3のコード内で実行が継続されることを意味します。これは偶然ではありません。そのように設計されました。なぜこの決定がなされたのですか?これには、ブロックの自動ブレークセマンティックを使用する場合と比べて、どのような利点がありますか?理論的根拠は何でしたか?

回答:


150

多くの回答は、陳述を要求する理由として、抜け落ちる能力に焦点を当てているようですbreak

それは単にCが設計されたときに、これらの構成体がどのように使用されるかについてほとんど経験がなかったために、それは単に間違いであったと思います。

ピーター・ファン・デル・リンデンは、彼の著書「エキスパートCプログラミング」でそのことを述べています。

Sun Cコンパイラのソースを分析して、デフォルトのフォールスルーが使用された頻度を確認しました。Sun ANSI Cコンパイラのフロントエンドには244のswitchステートメントがあり、それぞれに平均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#では、各caseブロックの最後に明示的なジャンプステートメントを必要とするのは良い考えだと思います(ただし、ステートメントのブロックが1つしかない限り、複数のcaseラベルをスタックできます)。C#では、1つのケースを別のケースにフォールスルーすることができます-を使用して次のケースにジャンプして、フォールスルーを明示的にする必要がありgotoます。

JavaがCのセマンティクスから脱却する機会を得なかったのは残念です。


確かに、彼らは実装の単純さを追求したと思います。他のいくつかの言語は、おそらく効率を犠牲にして、より洗練されたケース(範囲、複数の値、文字列...)をサポートしていました。
PhiLho 2008年

Javaはおそらく習慣を破り、混乱を広げたくありませんでした。別の動作の場合、彼らは異なるセマンティクスを使用する必要があります。とにかく、Javaデザイナーは、Cから脱却する多くの機会を失いました。
PhiLho 2008年

@PhiLho-あなたはおそらく「実装の単純さ」で真実に最も近いと思います。
マイケルバー

GOTOを介して明示的なジャンプを使用している場合、一連のIFステートメントを使用するだけで生産性が向上しませんか?
DevinB 2009

3
Pascalでさえ、中断することなくスイッチを実装します。どのようにCコンパイラコーダーはそれについて考えられないのでしょうか@@
Chan Le

30

多くの点で、cは標準のアセンブリイディオムへのクリーンなインターフェイスです。ジャンプテーブル駆動のフロー制御を作成する場合、プログラマは「制御構造」から抜け出すか、ジャンプするかを選択でき、ジャンプアウトには明示的な命令が必要です。

したがって、cは同じことを行います...


1
多くの人がそう言うのを知っていますが、それは全体像ではないと思います。Cは、多くの場合、アンチパターンのアセンブリへの醜いインターフェースでもあります。1.醜い:式ではなくステートメント。「宣言は使用を反映しています。」モジュール化と移植性のためメカニズムとして美化されたコピーペースト。型システムの方法は複雑すぎます。バグを助長します。NULL。(次のコメントに続きます)
ハリソン、

2.アンチパターン:アセンブリの2つの基本的なデータ表現イディオムの1つである、「クリーン」なタグ付きユニオンを提供しません。(Knuth、第1巻、第2節)代わりに、「タグなしのユニオン」、非慣用的なハック。この選択は、人々が何十年もの間データについて考える方法を妨げてきました。また、-でNUL終了する文字列は、これまでで最悪の考えにすぎません。
Harrison、

@HarrisonKlaperman:文字列を保存する方法は完璧ではありません。文字列を受け入れたルーチンが最大長パラメーターも受け入れた場合、ヌル終了文字列に関連する問題の大部分は発生せず、バッファーサイズパラメーターを受け入れずに長さマーク付きの文字列を固定サイズバッファーに格納するルーチンで発生します。ケースが単にラベルであるswitchステートメントのデザインは、現代の人には奇妙に思えるかもしれませんが、Fortran DOループのデザインよりも悪くはありません。
スーパーキャット2012

アセンブリでジャンプテーブルを作成する場合は、ケースの値を取得して、魔法のようにそれをジャンプテーブルの添え字に変え、その場所にジャンプして、コードを実行します。その後、私は次のケースにジャンプしません。すべてのケースの出口である統一アドレスにジャンプします。私が次の事件の本文に飛び込んだり落ち込んだりするという考えはばかげています。そのパターンのユースケースはありません。
EvilTeach 2014年

2
今日、人々は自分自身を足に撃ち込むのを妨げる清潔で自己防衛的なidosと言語機能にもっと慣れていますが、これはバイトが高価であった領域からの再考です(Cは1970年より前に始まりました)。コードが1024バイト以内に収まる必要がある場合は、コードフラグメントを再利用するための大きな予防策が発生します。同じ目的を共有する異なるエントリポイントから開始してコードを再利用することは、これを実現するための1つのメカニズムです。
rpy

23

ダフのデバイスを実装するには、明らかに:

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

3
ダフのデバイスが大好きです。とてもエレガントで、驚くほど速く。
dicroce、2008年

7
そうですが、SOにswitchステートメントがあるたびに表示される必要がありますか?
billjamesdev 2008年

閉じ中括弧が2つありません;-)。
Toon Krijthe、2008年

18

ケースが暗黙のうちに壊れるように設計されている場合、フォールスルーはあり得ません。

case 0:
case 1:
case 2:
    // all do the same thing.
    break;
case 3:
case 4:
    // do something different.
    break;
default:
    // something else entirely.

スイッチがすべてのケースの後で暗黙的に発生するように設計されている場合は、それについて選択することはできません。スイッチケース構造は、より柔軟になるように設計されています。


12
暗黙的にブレークするが、「フォールスルー」キーワードを持つスイッチをイメージできます。ぎこちないが実行可能。
dmckee ---元モデレーターの子猫

それはどうですか?私は、ケースステートメントが「ケースごとのコードのブロック」よりも頻繁にこの方法で機能することを想像します...それはif / then / elseブロックです。
billjamesdev 2008年

それを質問に追加します。「while」ステートメントのスタイルのように見えるので、各ケースブロックのスコープエンクロージャ{}は混乱を増しています。
マシュー・スミス

1
@Bill:もっと悪いと思いますが、Mike Bが提起した苦情に対処します。そのフォールスルー(複数のケースが同じ場合を除く)はまれなイベントであり、デフォルトの動作ではありません。
dmckee ---元モデレーターの子猫

1
簡潔にするために中括弧は省略しましたが、複数のステートメントがあるはずです。フォールスルーは、ケースがまったく同じ場合にのみ使用する必要があることに同意します。フォールスルーを使用してケースが以前のケースに基づいて構築されると、地獄のように混乱します。
リザードに請求する

15

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;

2
2つの大きな問題があります。1)break必要な場所を忘れる。2)caseステートメントの順序が変更されている場合、フォールスルーにより誤ったケースが実行される可能性があります。したがって、C#の処理の方がはるかに優れています(goto case空のケースラベルを除いて、フォールスルーの明示)。
ダニエルローズ

@DanielRose:1)breakC#でも忘れる方法があります-最も単純な方法は、case何も実行したくないが、追加するのを忘れたbreak場合です(おそらく、説明コメントに包まれたか、いくつかの場所で呼び出されました)他のタスク):実行はcase以下に該当します。2)励ますgoto caseは、非構造化「スパゲッティ」コーディングを奨励することですcase A: ... goto case B; case B: ... ; goto case A;。特に、ケースがファイル内で分離されており、組み合わせが困難な場合、偶発的なサイクル()が発生する可能性があります。C ++では、フォールスルーはローカライズされています。
Tony Delroy、2016年

8

次のようなことを許可するには:

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ラベルと考えてください。そうすれば、より自然なものになります。


8
if(0)最初のケースの終わりにそれは悪であり、目的がコードを難読化することである場合にのみ使用されるべきです。
ダニエルローズ

11
この全体の答えは悪であることの練習だったと思います。:-)
R .. GitHub ICEのヘルプを停止

3

複数のケースで同じコード(または同じコードを順番に)実行する必要がある場合、コードの重複を排除します。

アセンブリ言語レベルでは、それぞれの間で中断するかどうかは関係ないので、フォールスルーケースのオーバーヘッドはとにかくゼロなので、特定のケースで大きな利点があるため、許可しないでください。


2

私はたまたまベクターの値を構造体に割り当てる場合に遭遇しました:データベクトルが構造体のデータメンバーの数よりも短い場合、メンバーの残りはそのままになるような方法で行われなければなりませんでしたそれらのデフォルト値。その場合、省略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);
}

0

ここで多くが指定しているように、1つのコードブロックが複数のケースで機能できるようにするためです。これ、例で指定する「ケースごとのコードのブロック」よりも、switchステートメントのより一般的なオカレンスになるです。

フォールスルーのないケースごとのコードブロックがある場合は、if-elseif-elseブロックを使用することを検討してください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.