Switchステートメント:デフォルトは最後のケースでなければなりませんか?


178

次のswitchステートメントを検討してください。

switch( value )
{
  case 1:
    return 1;
  default:
    value++;
    // fall-through
  case 2:
    return value * 2;
}

このコードはコンパイルされますが、C90 / C99に対して有効(=定義済みの動作)ですか?デフォルトのケースが最後のケースではないコードを見たことがありません。

編集:
として、ジョン・ケージKillianDS書き込み:これは本当に醜いと混乱のコードであり、私はそれをよく知っています。一般的な構文(定義されているか?)と期待される出力に興味があります。


19
+1その行動さえ考慮しなかった
ジェイミーウォン

@PéterTörök:値== 2の場合、6を返しますか?
Alexandre C.

4
@PéterTörökいいえ、順序は重要ではありません。値がいずれかのケースラベルの定数と一致する場合、コントロールはラベルに続くそのステートメントにジャンプします。そうでない場合、コントロールはデフォルトラベルに続くステートメントにジャンプします(存在する場合)。
ピートカーカム

11
@ジョンケージgotoは悪ではありません。貨物カルト信者はいます!gotoこれは非常に邪悪であり、コードを実際に読めない状態にするので、人々がどの程度極端に回避できるか想像できませんでした。
PatrickSchlüter10年

3
私はgoto主にfinally、停止時にリソース(ファイル、メモリ)を解放する必要がある関数の句のようなものをシミュレートするために使用し、すべてのエラーの場合に繰り返しリストを繰り返しますが、読みやすさの助けにはfreeなりcloseません。goto回避したいけれどもできない1つの使用法がありますが、ループから抜け出したくてswitch、そのループ内にいるときです。
PatrickSchlüter、2010年

回答:


83

C99標準はこれについて明確ではありませんが、すべての事実をまとめると、完全に有効です。

A casedefaultlabelはlabelと同等gotoです。6.8.1ラベル付きステートメントを参照してください。特に興味深いのは6.8.1.4であり、これはすでに述べたダフのデバイスを有効にします。

ステートメントの前には、識別子をラベル名として宣言する接頭辞を付けることができます。ラベル自体は、制御の流れを変更することはありません。制御の流れは妨げられずに継続します。

編集:スイッチ内のコードは特別なものではありません。これは、if-statementのような通常のコードブロックであり、ジャンプラベルが追加されています。これは、フォールスルー動作とそのbreak必要性を説明しています。

6.8.4.2.7も例を示します:

switch (expr) 
{ 
    int i = 4; 
    f(i); 
case 0: 
    i=17; 
    /*falls through into default code */ 
default: 
    printf("%d\n", i); 
} 

人工プログラムフラグメントでは、識別子がiであるオ​​ブジェクトは自動ストレージ期間(ブロック内)で存在しますが、初期化されることはないため、制御式にゼロ以外の値がある場合、printf関数の呼び出しは不確定値にアクセスします。同様に、関数fの呼び出しにも到達できません。

ケース定数は、switchステートメント内で一意である必要があります。

6.8.4.2.3各ケースラベルの式は整数定数式である必要があり、同じスイッチステートメント内の2つのケース定数式が変換後に同じ値を持つことはできません。switchステートメントには、最大で1つのデフォルトラベルを含めることができます。

すべてのケースが評価され、指定されている場合はデフォルトのラベルにジャンプします。

6.8.4.2.5整数昇格は制御式で実行されます。各ケースラベルの定数式は、制御式の昇格された型に変換されます。変換された値が昇格された制御式の値と一致する場合、制御は一致したcaseラベルに続くステートメントにジャンプします。それ以外の場合で、デフォルトのラベルがある場合、制御はラベル付きステートメントにジャンプします。変換された大文字と小文字の定数式が一致せず、デフォルトのラベルがない場合、スイッチ本体のどの部分も実行されません。


6
@HeathHunnicutt例の目的を明確に理解していませんでした。コードはこのポスターで構成されていませんが、Cステートメントから直接取られており、奇妙なswitchステートメントがどのように変わっているか、そして悪い習慣がどのようにバグにつながるかを示しています。コードの下のテキストを読みたくなかったとしても、気がつくでしょう。
ランディン2013年

2
+1して反対投票を補います。C標準を引用することから誰かに反対票を投じることは、かなり厳しいようです。
ランディン2013年

2
@Lundin私はC標準に反対票を投じていません。そして、あなたが提案するように何も見落としませんでした。私は、悪い例と不要な例を使って、悪い教授法に反対票を投じました。特に、その例は、質問されたのとはまったく異なる状況に関連しています。続行できますが、「フィードバックをありがとう」
ヒースハニカット2013年

12
Intelは、分岐とループの再編成でswitchステートメントの最も頻繁に使用されるコードを最初に配置して、予測ミスを防止するように指示しています。私がここにいるのは、default他のケースを約100:1で独占しているケースがありdefault、最初のケースを作成するためにそのケースが有効か未定義かわからないためです。
jww 2016

@jww Intelがどういう意味かわかりません。もしあなたが知性を意味するなら、私はそれを仮説と呼びます。私も同じ考えでしたが、後で読むと、ifステートメントとは異なり、switchステートメントはランダムアクセスです。したがって、最後のケースは最初のケースよりも到達に時間がかかりません。これは、一定のケース値をハッシュすることによって実現されます。そのため、分岐が多い場合、switchステートメントの方がifステートメントよりも高速です。

91

caseステートメントとdefaultステートメントは、switchステートメント内で任意の順序で出現できます。default句は、caseステートメントのどの定数も一致しない場合に一致するオプションの句です。

良い例え :-

switch(5) {
  case 1:
    echo "1";
    break;
  case 2:
  default:
    echo "2, default";
    break;
  case 3;
    echo "3";
    break;
}


Outputs '2,default'

ケースをコード内で論理的な順序で提示したい場合(ケース1、ケース3、ケース2 /デフォルトではないなど)、ケースが非常に長く、ケース全体を繰り返したくない場合に非常に便利です。デフォルトの下部にあるコード


7
これはまさに私が通常デフォルトを最後以外の場所に置くシナリオです...明示的なケース(1、2、3)には論理的な順序があり、デフォルトが明示的なケースの1つとまったく同じように動作するようにしたい最後のものではありません。
ArtOfWarfare

51

これは有効で、場合によっては非常に便利です。

次のコードを検討してください。

switch(poll(fds, 1, 1000000)){
   default:
    // here goes the normal case : some events occured
   break;
   case 0:
    // here goes the timeout case
   break;
   case -1:
     // some error occurred, you have to check errno
}

ポイントは、上記のコードはカスケードよりも読みやすく効率的であるということifです。default最後に置くこともできますが、通常のケースではなくエラーケースに注意が向けられるため、無意味です(これが当てはまりますdefault)。

実際には、poll最大でいくつのイベントが発生するかを知っているので、それはそのような良い例ではありません。私の本当のポイントは、「例外」と通常のケースある、定義された入力値のセットを持つケースがあるということです。例外や通常のケースを前に置く方がよいかどうかは、選択の問題です。

ソフトウェアの分野では、非常に一般的な別のケースについて考えます。いくつかの最終的な値を持つ再帰です。スイッチを使用してそれを表現できる場合、default再帰呼び出しと識別された要素(個々のケース)の端末値を含む通常の値になります。通常、最終的な値に焦点を合わせる必要はありません。

別の理由は、ケースの順序がコンパイルされたコードの動作を変更する可能性があり、それがパフォーマンスにとって重要であることです。ほとんどのコンパイラは、コードがスイッチに表示されるのと同じ順序でコンパイル済みアセンブリコードを生成します。これにより、最初のケースが他のケースと大きく異なります。最初のケースを除くすべてのケースでジャンプが発生し、プロセッサパイプラインが空になります。ブランチプレディクタがデフォルトでスイッチに最初に現れるケースを実行するようにそれを理解しているかもしれません。他のケースよりもはるかに一般的なケースの場合は、それを最初のケースとするのに十分な理由があります。

コメントを読むのは、コードの最適化に関するインテルコンパイラーブランチループの再編成を読んだ後、元の投稿者がその質問をした理由です。

次に、コードの可読性とコードのパフォーマンスの間の調停になります。おそらく、ケースが最初に現れる理由を将来の読者に説明するためにコメントを付ける方が良いでしょう。


6
フォールスルー行動のない(良い)例を示すための+1。
KillianDS、2010年

1
...しかし、それについて考えると、非常に少数の人々がそこにそれを探しているだろうから、私はデフォルトがトップにあるのが良いとは確信していません。戻り値を変数に割り当て、ifの片側で成功を処理し、caseステートメントで反対側でエラーを処理することをお勧めします。
Jon Cage

@ジョン:ただそれを書いてください。読みやすさを向上させることなく、構文ノイズを追加します。そして、デフォルトが一番上にある場合、それを見る必要はまったくありません。それは本当に明白です(それを真ん中に置くと、よりトリッキーになる可能性があります)。
クリス

ちなみに、Cのswitch / case構文はあまり好きではありません。私は、複数のラベルを連続して貼るのではなく、1つのケースの後に複数のラベルを貼れるようにしたいと思っていますcase。憂鬱なのは、それが構文上の砂糖のように見え、サポートされていれば既存のコードを壊さないことです。
クリス

1
@kriss:「私もpythonプログラマではない!」:)
Andrew Grimm

16

はい、これは有効であり、いくつかの状況下ではそれはさらに有用です。一般的に、それが必要ない場合は、行わないでください。


-1:これは私に悪の匂いがします。コードを1組のswitchステートメントに分割することをお勧めします。
Jon Cage

25
@John Cage:ここに-1を付けるのは厄介です。これが有効なコードであることは私のせいではありません。
Jens Gustedt、2010年

好奇心旺盛ですが、どのような状況下でそれが役立つのか知りたいのですが?
サリル

1
-1は、有用であるというあなたの主張を目的としています。申し立てを裏付ける有効な例を提供できる場合は、+ 1に変更します。
Jon Cage

4
あるシステム関数から返されたerrnoに切り替えるとき。クリーンエグジットを実行する必要があることがわかっているケースが1つありますが、このクリーンエグジットでは、繰り返したくないコード行が必要になる場合があります。しかし、個別に処理したくないエキゾチックなエラーコードが他にもたくさんあるとします。私はデフォルトのケースでperrorを置くことを検討し、それを他のケースに実行させ、きれいに終了します。そのようにするべきだとは言わない。それは単に好みの問題です。
Jens Gustedt、2010年

8

switchステートメントに定義された順序はありません。あなたはケースをラベルのような名前付きラベルのようなものとして見るかもしれませんgoto。人々がここで考えているように見えるのとは逆に、値2の場合、デフォルトのラベルにはジャンプしません。古典的な例で説明するために、ここにCの両極端のポスターの子であるダフのデバイスがありますswitch/case

send(to, from, count)
register short *to, *from;
register count;
{
  register 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);
  }
}

4
そして、ダフのデバイスに慣れていない人にとっては、このコードは完全に判読できません...
KillianDS

7

caseステートメントの最後以外の場所に「デフォルト」を配置するのが適切だと私が考える1つのシナリオは、無効な状態がマシンをリセットし、それが初期状態であるかのように続行する状態マシンです。例えば:

スイッチ(widget_state)
{
  デフォルト:/ *レールから落ちた-リセットして続行* /
    widget_state = WIDGET_START;
    / *フォールスルー* /
  ケースWIDGET_START:
    ...
    ブレーク;
  ケースWIDGET_WHATEVER:
    ...
    ブレーク;
}

無効な状態がマシンをリセットするのではなく、無効な状態として容易に識別できる場合の代替の配置:

スイッチ(widget_state) { ケースWIDGET_IDLE: widget_ready = 0; widget_hardware_off(); ブレーク; ケースWIDGET_START: ... ブレーク; ケースWIDGET_WHATEVER: ... ブレーク; デフォルト: widget_state = WIDGET_INVALID_STATE; / *フォールスルー* / ケースWIDGET_INVALID_STATE: widget_ready = 0; widget_hardware_off(); ...「安全な」状態を確立するために必要な他のことを行う }

次に、他のコードが(widget_state == WIDGET_INVALID_STATE)をチェックして、エラー報告または状態リセットの動作が適切と思われるものを提供します。たとえば、ステータスバーコードにエラーアイコンが表示され、アイドル状態以外のほとんどの状態で無効になっている[スタートウィジェット]メニューオプションをWIDGET_INVALID_STATEおよびWIDGET_IDLEに対して有効にすることができます。


6

別の例を示します:これは、「デフォルト」が予期しないケースであり、エラーをログに記録したいが、賢明なこともしたい場合に役立ちます。私自身のコードの例:

  switch (style)
  {
  default:
    MSPUB_DEBUG_MSG(("Couldn't match dash style, using solid line.\n"));
  case SOLID:
    return Dash(0, RECT_DOT);
  case DASH_SYS:
  {
    Dash ret(shapeLineWidth, dotStyle);
    ret.m_dots.push_back(Dot(1, 3 * shapeLineWidth));
    return ret;
  }
  // more cases follow
  }

5

ENUMを文字列に変換したり、ファイルへの書き込みやファイルからの読み取りを行う場合に文字列を列挙型に変換したりする場合があります。

ファイルを手動で編集することにより発生するエラーに対応するために、デフォルト値の1つにする必要がある場合があります。

switch(textureMode)
{
case ModeTiled:
default:
    // write to a file "tiled"
    break;

case ModeStretched:
    // write to a file "stretched"
    break;
}

2

default条件は、ケース句が存在することができ、スイッチ内のどこであってもよいです。最後の句である必要はありません。デフォルトを最初の句とするコードを見たことがあります。case 2:デフォルトの句がそれを上回っているにもかかわらず、正常に実行されます。

テストとして、サンプルコードを関数に入れ、呼び出しtest(int value){}て実行しました。

  printf("0=%d\n", test(0));
  printf("1=%d\n", test(1));
  printf("2=%d\n", test(2));
  printf("3=%d\n", test(3));
  printf("4=%d\n", test(4));

出力は次のとおりです。

0=2
1=1
2=4
3=8
4=10

1

それは有効ですが、かなり厄介です。非常に厄介なスパゲッティコードにつながる可能性があるため、フォールスルーを許可することは一般的に悪いことをお勧めします。

ほとんどの場合、これらのケースをいくつかのswitchステートメントまたは小さな関数に分割する方が適切です。

[編集] @Tristopia:あなたの例:

Example from UCS-2 to UTF-8 conversion 

r is the destination array, 
wc is the input wchar_t  

switch(utf8_length) 
{ 
    /* Note: code falls through cases! */ 
    case 3: r[2] = 0x80 | (wc & 0x3f); wc >>= 6; wc |= 0x800; 
    case 2: r[1] = 0x80 | (wc & 0x3f); wc >>= 6; wc |= 0x0c0; 
    case 1: r[0] = wc;
}

次のように書かれていれば、その意図について(私は思う)より明確になります。

if( utf8_length >= 1 )
{
    r[0] = wc;

    if( utf8_length >= 2 )
    {
        r[1] = 0x80 | (wc & 0x3f); wc >>= 6; wc |= 0x0c0; 

        if( utf8_length == 3 )
        {
            r[2] = 0x80 | (wc & 0x3f); wc >>= 6; wc |= 0x800; 
        }
    }
}   

[編集 2 ] @Tristopia:2番目の例は、フォロースルーの優れた使用法のおそらく最もクリーンな例です。

for(i=0; s[i]; i++)
{
    switch(s[i])
    {
    case '"': 
    case '\'': 
    case '\\': 
        d[dlen++] = '\\'; 
        /* fall through */ 
    default: 
        d[dlen++] = s[i]; 
    } 
}

..しかし、個人的にはコメント認識を独自の機能に分割します:

bool isComment(char charInQuestion)
{   
    bool charIsComment = false;
    switch(charInQuestion)
    {
    case '"': 
    case '\'': 
    case '\\': 
        charIsComment = true; 
    default: 
        charIsComment = false; 
    } 
    return charIsComment;
}

for(i=0; s[i]; i++)
{
    if( isComment(s[i]) )
    {
        d[dlen++] = '\\'; 
    }
    d[dlen++] = s[i]; 
}

2
フォールスルーが本当に、本当に良い考えである場合があります。
パトリックSchlüter

UCS-2からUTF-8への変換の例rは宛先配列、wc入力wchar_t スイッチです(utf8_length){/ *注:コードは大文字小文字を区別しません!* /ケース3:r [2] = 0x80 | (wc&0x3f); wc >> = 6; wc | = 0x800; ケース2:r [1] = 0x80 | (wc&0x3f); wc >> = 6; wc | = 0xc0; ケース1:r [0] = wc; }
PatrickSchlüter2010年

ここでは別の、文字エスケープと文字列コピールーチン: for(i=0; s[i]; i++) { switch(s[i]) { case '"': case '\'': case '\\': d[dlen++] = '\\'; /* fall through */ default: d[dlen++] = s[i]; } }
パトリックSchlüter

はい。ただし、このルーチンは私たちのホットスポットの1つです。これは、これを実装するための最速で移植可能な(アセンブリは行いません)方法でした。UTFの長さのテストは1つしかありませんが、あなたのテストでは2つまたは3つです。
パトリックSchlüter

1
はい、特にブルガリア語とギリシャ語(Solaris SPARC)での変換と、内部マークアップ(3バイトUTF8)を使用したテキストがありました。認めましたが、前回のハードウェアの更新以降、それほど多くはなく、無関係になりましたが、書かれた時点では、多少の違いがありました。
PatrickSchlüter、2010年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.