MSC20-C. 複雑なブロックに制御を渡す際に switch 文を使用しない
ある case 句内でコードブロックを開始し、そのブロック内に別の case 句を配置するというように、switch 文とコードブロックが入り混じった状態にすることが可能である。この状態は、1 つのブロックが複数の case 句をまたいだ状態と見ることもできる。
C99 のセクション 6.8.6.1 「goto 文」は以下のように規定している。
goto 文は、可変修飾型を持つ識別子の有効範囲の外側からその識別子の有効範囲の内側へ分岐してはならない。
しかし規格では、可変修飾型の識別子が関与しないループに、goto または switch を使用してジャンプすることは禁止していない。結果として、ループやその他のコードブロックを switch 文と自由に混在させることができる。残念ながら、これによってコードは紛らわしく動作がわかりにくいものとなり、望ましくない動作を引き起こしかねない。
ここで紹介している例は、「MSC17-C. case 句に関連付けられた一連の文は break 文で終了する」の例外 MSC17-EX2 に該当する。
違反コード
以下のコード例は switch 文を使用して for ループにジャンプする。
int f(int i) { int j=0; switch (i) { case 1: for(j=0;j<10;j++) { // break を実行せず case 2 を処理する case 2: // for ブロックの内側にジャンプする j++; // break を実行せず case 3 を処理する case 3: j++; } break; default: // default の動作 break; } return j; }
処理系固有の詳細
i=1 のときは、for ループ全体を実行する。i=2 のときは、ループの開始前に j が 2 つインクリメントされる。i=3 のときは、ループの開始前に j が 1 つインクリメントされる。デフォルトケースはループなしである。その結果、関数は以下のような動作をする。
i | f() |
---|---|
1 | 12 |
2 | 12 |
3 | 11 |
その他の値 | 0 |
適合コード
以下の解決法は switch と for を切り離している。
int f(int i) { int j=0; switch (i) { case 1: // break を実行せず case 2 を処理する case 2: j++; // break を実行せず case 3 を処理する case 3: j++; break; default: // デフォルトの動作 return j; } for(j++;j<10;j++) { j+=2; } return j; }
違反コード (Duff's Device)
Duff's Device は、一連のコピーを実行するためのコードに適用される興味深い最適化である。つまり、一連のバイトを 1 つのメモリ出力に順番にコピーする。このための簡単なコードが以下である。
size_t count; /* ゼロ以外の値でなければならない */ char *to; /* 出力先 */ char *from; /* コピーするバイト数を指す */ do { *to = *from++; /* ''to'' ポインタがインクリメントされない点に注意 */ } while (--count > 0);
ただし、このコードでは while 条件が count 回実行されるため、速度が非常に遅くなることがある。伝統的な Duff's Device のコードは、このループを解いて比較実行回数を少なくする。
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); }
このコードでは、switch 文によって最初のループが実行されるので、count % 8 回の代入を実行する。それ以降のループでは毎回 8 回の代入が実行される(ループの範囲外になると、switch 文は無視される)。結果的には、このコードは count 回の代入を実行するが、比較は n 回しか行わないため、通常はこちらのほうが高速である。
このコードは正当な C および C++ コードと見なされ、すべての適合コンパイラで使用できる。Duff's Device についての説明で、作成者は以下のように述べている。
C 言語の最悪の機能は、switch が各 case 句の末尾で自動的に分岐しない点だと多くの人が指摘している。このコードはこの点についての議論を生じさせるが、この議論を肯定することになるのか、否定することになるのかは定かではない。
適合コード (Duff's Device)
以下は switch 文とループを分離する Duff's Device のもう 1 つの実装方法である。
int n = (count + 7) / 8; switch (count % 8) { case 0: *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) { *to = *from++; *to = *from++; *to = *from++; *to = *from++; *to = *from++; *to = *from++; *to = *from++; *to = *from++; }
リスク評価
レコメンデーション | 深刻度 | 可能性 | 修正コスト | 優先度 | レベル |
---|---|---|---|---|---|
MSC20-C | 中 | 中 | 中 | P8 | L2 |
参考情報
- [ISO/IEC 9899:1999] Section 6.8.6.1 "The goto statement"
- [MISRA 04] Rule 15.1
- Tom Duff on Duff's Device
翻訳元
これは以下のページを翻訳したものです。
MSC20-C. Do not use a switch statement to transfer control into a complex block