LWP | C言語のswitchにbreakが必要な理由
LWP TECHNICAL ARTICLE | 174
C言語のswitchにbreakが必要な理由
fall-throughを「設計ミス」で片づけず、caseの構造とBCPLからの歴史で読み解く
Copyright © 2026 LWP 山中 一弘
本資料は、出典を明記いただければ、商用・非商用を問わず、ご自由に複製・改変・再配布していただけます。なお、著作権表示は改変せず、そのまま記載してご利用くださいますようお願いいたします。
記事要約
C言語のswitchは、breakを書かなければ次のcaseへ進みます。現代の感覚では、各caseが独立した分岐ブロックであり、処理が終われば自動的にswitchを抜ける方が自然に見えます。しかし初期Cのcaseは、処理の終端を表すブロックではなく、実行開始位置を示すラベルです。一致したラベルへ制御を移した後は、明示的に止めない限り、通常の逐次実行が続きます。
この仕様は、Cで白紙から発明されたものでも、熟練者向けの技巧を優先して単純に選ばれたものでもありません。Dennis Ritchieの言語史によれば、Bell Labsが学んだ初期BCPLには後のendcaseがなく、BとCではbreakをswitchの脱出にも使う進化が生じました。一方、Brian Kernighanは1974年のCチュートリアルで、fall-throughには複数のcaseをまとめられる利点と、break忘れを招く欠点があると整理しています。
結論は、Cのfall-throughは「歴史的に継承されたラベル構造」と「実用上の表現力」が重なって定着した仕様だということです。現在のコードでは、意図しないfall-throughを避け、必要な場合だけコメントやC23の[[fallthrough]]で意図を明示するのが安全です。
本記事の対象とゴール
想定読者
C言語のswitchで、なぜ各caseにbreakが必要なのか疑問を持った方
fall-throughを単なる古い仕様ではなく、制御構造として理解したい方
BCPL、B、Cのつながりから言語仕様の成り立ちを学びたい方
本記事で得られること
caseがブロックではなくラベルであることを、実行順序から説明できます。
CのbreakがBCPL、B、Cの歴史の中で定着した経緯を理解できます。
意図的なfall-throughとbreak忘れを区別し、現代のCで安全に書く判断軸を持てます。
目次
はじめに なぜbreakが必要なのか
第1章 caseは処理ブロックではなくラベルである
第2章 BCPLからB、Cへ受け継がれた構造
第3章 fall-throughが持つ二つの利点
第4章 最大の欠点は意図が読めないこと
第5章 現代のCでは意図を明示する
第6章 言語仕様を歴史と構造の両方から読む
まとめ breakは歴史と設計の境界にある
はじめに なぜbreakが必要なのか
典型的なCのswitchは、次のように書きます。
switch (x) {
case 1:
foo();
break;
case 2:
bar();
break;
default:
baz();
break;
}
ここで素朴な疑問が生じます。case 1の処理が終わったのなら、なぜ自動的にswitchを抜けないのでしょうか。
実際、breakを削ると挙動が変わります。
switch (x) {
case 1:
foo();
case 2:
bar();
break;
}
x == 1なら、foo()の後にbar()も実行されます。これがfall-throughです。
この動きを理解する第一歩は、「fall-throughという特別な命令がある」と考えないことです。Cでは、一致したcaseから通常の逐次実行が始まり、breakがその流れをswitchの外へ移します。
第1章 caseは処理ブロックではなくラベルである
1.1 caseが決めるのは開始位置である
現代のプログラマは、caseを独立した分岐ブロックとして読みがちです。しかし初期Cの仕様では、caseとdefaultは文に付ける接頭ラベルです。
Dennis Ritchieの初期Cリファレンスマニュアルは、caseまたはdefaultの接頭辞そのものは制御の流れを変えないと説明しています。switch式と一致したcaseが見つかると、そのラベルに続く文へ制御が移ります。そこから先は、通常の文と同じように上から下へ実行されます。
switch (x) {
case 1:
A();
case 2:
B();
case 3:
C();
}
x == 2なら、概念的な実行順序は次のようになります。
case 2へ制御を移す
↓
B()を実行する
↓
次の文であるC()を実行する
↓
switchの末尾に到達する
次のcaseに到達したから特別に「落ちる」のではありません。ラベルは実行中の流れを止めないため、次の文へ進んだ結果として次のcaseを通過します。
1.2 特別な制御移動を行うのはbreakである
この構造では、breakの役割が明確になります。
case
一致したときの開始位置を示す
break
最も内側のswitchまたは反復文を終了する
したがって、Cのswitchは「各分岐が自動終了し、必要なら継続する」構造ではありません。「一致したラベルから実行を開始し、必要なら明示的に脱出する」構造です。
第2章 BCPLからB、Cへ受け継がれた構造
2.1 Cで突然発明された仕様ではない
Cの直接の祖先はBであり、BはBCPLから強い影響を受けています。
BCPL
Martin Richards
↓
B
Ken Thompson
↓
C
Dennis Ritchie
Brian KernighanのB言語チュートリアルには、すでにswitch、case、breakが登場します。そこでは、一致したcaseから実行が始まり、別のcaseへfall-throughでき、breakがswitchの直後へ制御を移すと説明されています。
つまり、Cのfall-throughを理解するには、Cだけを見て「なぜこの設計を選んだのか」と問うだけでは足りません。Cが受け継いだBの構造まで戻る必要があります。
2.2 初期BCPLには後のendcaseがなかった
Ritchieは『The Development of the C Language』で、Bell Labsの開発者が1960年代に学んだ初期BCPLには、後の版にあるendcaseがまだ存在しなかったと説明しています。その結果、BとCではbreakをswitchからの脱出にも使うようになりました。
Ritchieは、この違いを「意図的変更というより分岐した進化」と表現しています。これは、設計者が自動終了とfall-throughを白紙から比較し、fall-throughの方が優れていると結論した、という単純な歴史ではないことを示します。
Bell Labsが学んだ初期BCPL
endcaseがまだない
↓
B
caseはラベル
breakでswitchを抜ける
↓
C
基本構造を継承する
2.3 小さな言語と単純な実装という背景
Ritchieは、BCPL、B、Cを、システムプログラミング向けで、小さく記述でき、単純なコンパイラで翻訳しやすい言語群として説明しています。また、Bのコンパイラ設計にはメモリ制約が強く影響していました。
ただし、ここから「メモリを節約するためにfall-throughを採用した」と断定してはいけません。一次資料が直接述べているのは、初期BCPLにendcaseがなかったことと、B/Cのbreakが分岐した進化から生じたことです。小さな実装環境は言語全体の背景であり、fall-through単独の採用理由ではありません。
第3章 fall-throughが持つ二つの利点
3.1 複数の条件を一つの処理へまとめる
fall-throughの分かりやすい利点は、複数のcaseを同じ処理へまとめられることです。
switch (c) {
case 'a':
case 'A':
handle_a();
break;
}
このコードは、cが小文字のaでも大文字のAでも同じ処理を実行します。Kernighanは1974年のCチュートリアルで、まさに大文字と小文字を同じ処理へまとめる例をfall-throughの利点として示しています。
ここでは、最初のcaseに実行文がありません。二つのラベルが同じ文を指していると理解すると自然です。
3.2 複数の入口から共通処理へ合流する
もう一つの利点は、処理の途中から共通部分へ合流できることです。
switch (x) {
case 1:
special_for_1();
/* case 2の共通処理へ続ける */
case 2:
common_for_1_and_2();
break;
}
x == 1なら固有処理の後に共通処理を実行し、x == 2なら共通処理から始めます。Bのチュートリアルにも、複数の文字を同じ処理へまとめたり、ある操作から別の共通処理へ続けたりする実例があります。
ただし、共通処理が長い場合や再利用範囲が広い場合は、別関数へ切り出した方が読みやすくなることがあります。fall-throughは、重複を減らせるから常に優れているのではなく、局所的な合流を簡潔に表現できる選択肢です。
第4章 最大の欠点は意図が読めないこと
4.1 意図的な継続とbreak忘れが同じ形になる
次のコードだけを見て、作者の意図を確定できるでしょうか。
switch (x) {
case 1:
foo();
case 2:
bar();
break;
}
考えられる意図は二つあります。
foo()の後にbar()も実行したい。
foo()の後で抜けるつもりだったが、breakを書き忘れた。
コードの見た目だけでは区別しにくい点が、fall-throughの最大の欠点です。Kernighanがこの仕様を、利点と欠点の両方を持つものと評価した理由もここにあります。
4.2 規模が大きくなるほど曖昧さの費用が増える
小さなプログラムでは、直後の数行を読めば意図を推測できるかもしれません。しかし、開発者が増え、保守期間が長くなり、静的解析やコードレビューを使う環境では、暗黙の意図は障害になります。
| 観点 | 暗黙のfall-throughで起きる問題 |
|---|---|
| コードレビュー | 意図的か欠落かを確認する必要がある |
| 静的解析 | 警告すべき箇所と許容すべき箇所を判別しにくい |
| 保守 | 後からcaseを追加したとき、実行範囲が変わりやすい |
| テスト | 一見もっともらしい副作用が出て、発見が遅れることがある |
仕様として表現力があることと、日常のコードで積極的に使うべきことは別問題です。
第5章 現代のCでは意図を明示する
5.1 通常はbreakまたはreturnで終了させる
各caseを独立した分岐として使うなら、break、return、その他の明確な制御移動で終了させます。
switch (command) {
case CMD_START:
start();
break;
case CMD_STOP:
stop();
break;
default:
report_unknown(command);
break;
}
この形では、読者は各caseを独立して追えます。
5.2 意図的なfall-throughは注記する
意図的に次のcaseへ続けるなら、少なくともコメントで意図を示します。
switch (x) {
case 1:
special_for_1();
/* fall through: case 2の共通処理も実行する */
case 2:
common_for_1_and_2();
break;
}
C23では、意図的なfall-throughを表す標準属性[[fallthrough]]を使えます。
switch (x) {
case 1:
special_for_1();
[[fallthrough]];
case 2:
common_for_1_and_2();
break;
}
利用するコンパイラとC言語モードがC23の該当機能に対応しているかは確認が必要です。旧規格や既存コンパイラでは、処理系固有の属性や、静的解析が認識するコメントを使う場合があります。
5.3 まず重複ラベル、次に関数分割を検討する
意図的なfall-throughには、用途ごとに読みやすい代替があります。
同じ処理をするだけなら、複数のcaseラベルを一つの文へ重ねます。
共通処理が長いなら、関数へ切り出します。
前段の処理を積み上げる必要があるときだけ、明示的なfall-throughを検討します。
「書けるか」ではなく、「読者が意図を一意に判断できるか」を基準にします。
第6章 言語仕様を歴史と構造の両方から読む
6.1 歴史だけでも、合理性だけでも説明しきれない
Cのfall-throughを「昔の設計ミス」とだけ呼ぶと、caseがラベルである構造と、複数入口をまとめられる利点を見落とします。逆に「柔軟で合理的な機能」とだけ評価すると、Ritchieが述べた歴史的経緯と、break忘れの危険を見落とします。
より正確な理解は、次の三段階です。
歴史的にラベル構造が受け継がれた
↓
実用上の利点が見いだされ、使われた
↓
大規模開発では曖昧さが欠点として強くなった
6.2 デフォルトの選び方は時代の価値観を映す
Cでは、通常は逐次実行が続き、終了したいときにbreakを明示します。より新しい設計では、通常は分岐ごとに終了し、続けたいときだけfall-throughを明示する方式が多く見られます。
違いは、機能の有無ではなく、どちらを既定にするかです。
Cの基本モデル
既定:次の文へ進む
例外:breakで抜ける
安全性を優先するモデル
既定:分岐を終了する
例外:継続を明示する
言語仕様は純粋な合理性だけで決まりません。祖先言語、既存実装、利用慣習、計算資源、保守規模などを引き継ぎながら形作られます。Cのswitchは、そのことを学ぶよい教材です。
まとめ breakは歴史と設計の境界にある
Cのcaseは独立した処理ブロックではなく、実行開始位置を示すラベルです。
一致したcaseから通常の逐次実行が始まるため、止めなければ次のcaseへ進みます。
breakはfall-throughを無効にする飾りではなく、switchの外へ制御を移す文です。
BとCのbreakは、Bell Labsが学んだ初期BCPLに後のendcaseがなかった歴史と結び付いています。
fall-throughには複数条件の統合や共通処理への合流という利点があります。
最大の欠点は、意図的な継続とbreak忘れが同じ形に見えることです。
現代のCでは、通常は明確に終了し、必要なfall-throughだけ意図を注記します。
switchのbreakは、単なる古い文法上の癖ではありません。ラベルとしての構造、言語の系譜、そして安全性に対する価値観の変化が一か所に表れた仕組みです。
出典メモ
元原稿: C言語_switch_break_fallthrough_歴史と設計_記事元ネタ.md
Dennis M. Ritchie, The Development of the C Language, Nokia Bell Labs: https://www.nokia.com/bell-labs/about/dennis-m-ritchie/chist.html
Brian W. Kernighan, Programming in C: A Tutorial (1974): https://www.lysator.liu.se/c/bwk-tutor.html
Brian W. Kernighan, A Tutorial Introduction to the Language B: https://www.bell-labs.com/usr/dmr/www/btut.pdf
Dennis M. Ritchie, C Reference Manual: https://www.bell-labs.com/usr/dmr/www/cman.pdf
ISO/IEC JTC1/SC22/WG14, C言語標準化作業部会とC23の案内: https://www.open-std.org/jtc1/sc22/wg14/
WG14 N2268, The fallthrough attribute: https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2268.pdf
取得・再確認日: 2026年8月17日
資料中の短い英語表現は論点の特定に必要な範囲だけ示し、本文は日本語で要約した。
「小さな実装環境がfall-through単独の採用理由だった」とは断定せず、言語全体の背景と直接の歴史的説明を分けた。
