web-dev-qa-db-ja.com

スイッチケースで有効ですが、価値のない構文ですか?

少し間違えて、誤ってこの構成を見つけました。

int main(void) {
    char foo = 'c';

    switch(foo)
    {
        printf("Cant Touch This\n");   // This line is Unreachable

        case 'a': printf("A\n"); break;
        case 'b': printf("B\n"); break;
        case 'c': printf("C\n"); break;
        case 'd': printf("D\n"); break;
    }

    return 0;
}

printfステートメントの先頭にあるswitchは有効であるように見えますが、完全に到達不能です。

到達不能なコードに関する警告さえも表示せずに、きれいにコンパイルしましたが、これは無意味なようです。

コンパイラはこれに到達不能コードとしてフラグを立てるべきですか?
これは何らかの目的を果たしていますか?

206
abelenky

おそらく最も有用ではないかもしれませんが、完全に無価値ではありません。 switchスコープ内で使用可能なローカル変数を宣言するために使用できます。

switch (foo)
{
    int i;
case 0:
    i = 0;
    //....
case 1:
    i = 1;
    //....
}

標準(N1579 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の呼び出しに到達できません。

P.S。ところで、サンプルは有効なC++コードではありません。その場合(N4140 6.7/3、エンファシスマイン):

ジャンプするプログラム90 自動ストレージ期間を持つ変数がスコープ内にないポイントからスコープ内にあるポイントまでは、変数の形式が不正です変数がスカラー型、簡単なデフォルトコンストラクタと簡単なデストラクタを持つクラスタイプ、これらのタイプのいずれかのcv修飾バージョン、または前述のタイプのいずれかの配列、イニシャライザなしで宣言されています(8.5)。


90)switchステートメントの条件からcaseラベルへの転送は、この点でジャンプと見なされます。

したがって、int i = 4;int i;に置き換えると、有効なC++になります。

225
AlexD

これは何の目的にも役立ちますか?

はい。ステートメントの代わりに、最初のラベルの前に宣言を置く場合、これは完全に理にかなっています:

switch (a) {
  int i;
case 0:
  i = f(); g(); h(i);
  break;
case 1:
  i = g(); f(); h(i);
  break;
}

宣言とステートメントのルールは一般的にブロックで共有されているため、ブロック内のステートメントも許可するのと同じルールです。


同様に言及する価値があるのは、最初のステートメントがループ構造である場合、caseラベルがループ本体に表示される可能性があることです。

switch (i) {
  for (;;) {
    f();
  case 1:
    g();
  case 2:
    if (h()) break;
  }
}

より読みやすい書き方がある場合は、このようなコードを書かないでください。しかし、それは完全に有効であり、f()呼び出しは到達可能です。

98
user743382

Duff's Device と呼ばれるこれの有名な使用法があります。

int n = (count+3)/4;
switch (count % 4) {
  do {
    case 0: *to = *from++;
    case 3: *to = *from++;
    case 2: *to = *from++;
    case 1: *to = *from++;
  } while (--n > 0);
}

ここで、fromが指すバッファーをtoが指すバッファーにコピーします。データのcountインスタンスをコピーします。

do{}while()ステートメントは、最初のcaseラベルの前に開始され、caseラベルはdo{}while()内に埋め込まれます。

これにより、do{}while()ループの終わりにある条件分岐の数が約4倍減少します(この例では、定数は任意の値に調整できます)。

現在、オプティマイザは時々これを行うことができます(特にストリーミング/ベクトル化された命令を最適化している場合)が、プロファイルガイドによる最適化がないと、ループが大きいかどうかを予測できません。

一般に、変数宣言はそこで発生し、あらゆる場合に使用できますが、スイッチの終了後はスコープ外になります。 (初期化はスキップされます)

さらに、スイッチ固有ではない制御フローは、上記のように、またはgotoを使用して、スイッチブロックのそのセクションに移動できます。

Linuxでgccを使用していると仮定すると、4.4以前のバージョンを使用している場合は警告が表示されます。

-Wunreachable-codeオプション gcc 4.4で削除されました 以降。

15
16tons

switchステートメント内のコード、またはこのコード内のcase *:ラベルの配置場所には、実質的に構造的な制限がないことに注意してください。これにより、 duff's device のようなプログラミングトリックが可能になります。その実装の1つは次のようになります。

int n = ...;
int iterations = n/8;
switch(n%8) {
    while(iterations--) {
        sum += *ptr++;
        case 7: sum += *ptr++;
        case 6: sum += *ptr++;
        case 5: sum += *ptr++;
        case 4: sum += *ptr++;
        case 3: sum += *ptr++;
        case 2: sum += *ptr++;
        case 1: sum += *ptr++;
        case 0: ;
    }
}

おわかりのように、switch(n%8) {case 7:ラベルの間のコードは間違いなく到達可能です...


As supercatがコメントで指摘されている :C99以降、VLA宣言を含む宣言のスコープ内にgotoもラベル(case *:ラベルであってもなくても)が表示されない場合があります。したがって、case *:ラベルの配置にno構造上の制限があると言うのは正しくありません。ただし、duffのデバイスはC99標準より前のものであり、とにかくVLAに依存しません。それにもかかわらず、私は最初の文に「事実上」を挿入せざるを得ないと感じました。

11
cmaster

変数宣言だけでなく、高度なジャンプにも使用できます。スパゲッティコードを使用する傾向がない場合にのみ、それをうまく利用できます。

int main()
{
    int i = 1;
    switch(i)
    {
        nocase:
        printf("no case\n");

        case 0: printf("0\n"); break;
        case 1: printf("1\n"); goto nocase;
    }
    return 0;
}

プリント

1
no case
0 /* Notice how "0" prints even though i = 1 */

Switch-caseは最速の制御フロー句の1つであることに注意してください。そのため、プログラマーにとっては非常に柔軟でなければならず、このような場合が時々含まれます。

11

gccオプションが必要です-Wswitch-unreachableに関連する回答がありました。この回答は、ユーザビリティ/価値部分。

C11、§6.8.4.2、(emphasis mine)から直接引用

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ステートメントスコープ内でのみ使用可能なローカルスコープの変数を定義できます。

10
Sourav Ghosh

それで「ループと半分」を実装することは可能ですが、それを行うための最良の方法ではないかもしれません。

char password[100];
switch(0) do
{
  printf("Invalid password, try again.\n");
default:
  read_password(password, sizeof(password));
} while (!is_valid_password(password));
9
celtschk