web-dev-qa-db-ja.com

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

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

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

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

編集:
AsJon CageおよびKillianDS書き込み:これは本当にくて紛らわしいコードであり、私はそれをよく知っています。一般的な構文(定義されていますか?)と期待される出力に興味があります。

162
tanascius

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

caseおよびdefaultラベルは、gotoラベルと同等です。 6.8.1ラベル付きステートメントを参照してください。特に興味深いのは6.8.1.4です。これにより、前述のDuffのデバイスが有効になります。

ステートメントの前には、識別子をラベル名として宣言するプレフィックスを付けることができます。ラベル自体は制御フローを変更せず、ラベル全体に影響を与え続けます。

編集:スイッチ内のコードは特別なものではありません。これは、if-文のような通常のコードブロックであり、追加のジャンプラベルがあります。これは、フォールスルー動作と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の呼び出しに到達できません。

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

6.8.4.2.3各caseラベルの式は整数定数式とし、同じswitchステートメント内の2つのcase定数式が変換後に同じ値を持たないようにします。 switchステートメントには、最大で1つのデフォルトラベルが存在する場合があります。

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

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

74
Secure

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 /デフォルトとは言いません)、ケースが非常に長いため、ケース全体を繰り返したくない場合に非常に便利ですデフォルトの下部のコード

78
Salil

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

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

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ケース)ではなくエラーケースに注意を集中するため、意味がありません。

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

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

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

コメントを読むことは、コードの最適化について Intelコンパイラーブランチループの再編成 を読んだ後に元のポスターがその質問をした特定の理由です。

その後、コードの可読性とコードのパフォーマンスの間の調停になります。ケースが最初に表示される理由を将来の読者に説明するコメントを入れる方が良いでしょう。

45
kriss

はい、これは有効であり、状況によってはさらに有用です。通常、必要ない場合は実行しないでください。

15
Jens Gustedt

Switchステートメントには順序が定義されていません。 gotoラベルのような名前付きラベルのようなものとしてケースを見ることができます。ここで人々が考えていることとは反対に、値2の場合、デフォルトのラベルはジャンプしません。古典的な例を説明するために、ここに Duffのデバイス があります。これは、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);
  }
}
8

Caseステートメントの終わり以外の場所に「デフォルト」を置くことが適切だと思うシナリオの1つは、無効な状態がマシンをリセットし、あたかも初期状態であるかのように進むべき状態マシンです。例えば:

 switch(widget_state)
 {
デフォルト:/ * Railsを終了-リセットして続行*/
 widget_state = WIDGET_START; 
/*フォールスルー*/
 case WIDGET_START:
 ... 
 break; 
 case WIDGET_WHATEVER:
 ... 
 break ; 
} 

無効な状態がマシンをリセットするべきではないが、無効な状態として容易に識別できるようにする必要がある場合の代替構成:

 
 
 switch(widget_state)
 {
 case WIDGET_IDLE:
 widget_ready = 0; 
 widget_hardware_off() ; 
 break; 
 case WIDGET_START:
 ... 
 break; 
 case WIDGET_WHATEVER:
 ... 
 break; 
 default:
 widget_state = WIDGET_INVALID_STATE; 
/* Fall through */
 case WIDGET_INVALID_STATE:
 widget_ready = 0; 
 widget_hardware_off(); 
 ...「安全な」状態を確立するために必要な他のことをすべて行います
} 

他の場所のコードは(widget_state == WIDGET_INVALID_STATE)を確認し、エラー報告または状態リセットの動作が適切と思われるものを提供します。たとえば、ステータスバーコードにはエラーアイコンが表示され、WIDGET_IDLEと同様に、WIDGET_INVALID_STATEに対して、ほとんどの非アイドル状態で無効になっている「ウィジェットの開始」メニューオプションが有効になります。

7
supercat

別の例で説明します。これは、「デフォルト」が予期しないケースであり、エラーをログに記録するだけでなく、適切な操作を行う場合に役立ちます。私自身のコードの例:

  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
  }
6
Brennan Vincent

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

手動でファイルを編集することによって生じたエラーをカバーするために、いずれかの値をデフォルトにする必要がある場合があります。

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

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

「デフォルト」条件は、case句が存在できるスイッチ内の任意の場所です。最後の句である必要はありません。最初の句としてデフォルトを置くコードを見てきました。 「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
2
Scott Thomson

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

これらのケースをいくつかの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; 
        }
    }
}   

[edit2] @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]; 
}
1
Jon Cage