読者です 読者をやめる 読者になる 読者になる

dangling else と、関連事項について

C言語のif文が関係する部分の構文規則を抜粋すると、次のようになっている。

<文>       = <IF文>
           | <ブロック>
           | <式文>
           | <空文>

<IF文>     = "if" "(" <式> ")" <文> ["else" <文>]

<ブロック>  = "{" <文> <文>... "}"

<式文>     = <式> ";"

<空文>     = ";"

<式>       = <演算子式> | etc(略)

この規則で、一番大きな問題だと一般にされがちなのは「dangling else 問題」である(この dangling は、「ぶらぶら」とか「宙ぶらりん」と訳すのが適切と思われる。RHG読書会::東京 ふつパイラ篇 の第2回に『dangleには「未解決の問題、分からないこと、腑に落ちない点」という訳もある。むしろそっちかも』という指摘があった)。
dangling else 問題とは、次のような問題である。次のようなソースコードがあったとする。

if (a == 1)
    x = 2;
    if (b == 3)
        y = 4;
    else
        z = 5;

このようにインデントすると、最後の else は if (b == 3) の if に対応するように見えるが、上の構文規則からは、次のようにも解釈できる。

if (a == 1)
    x = 2;
    if (b == 3)
        y = 4;
else
    z = 5;

文法的には、この問題の解決は簡単で、「elseは直前のifに対応する」という附則があることにすれば良い。文脈自由文法ではなく、プログラミング言語処理系の実装などによく使われる yacc の LALR(1) 文法なら、そのような自然言語による附則のようなものではなく、「shift/reduce conflict になるがその時は shift する」というデフォルトの振舞により解決される。
この問題は、一見すると、if文のthenとelseの帰結節を単に <文> とし、複数個の文をそこに置きたい時は <ブロック> にせよ、というような、一見美しい規則に潜むワナ、といったように見えるため、そのような規則の元祖であるAlgol(C言語の { } と違い begin end だが)に由来するもののように言われるが、実は Modified Report on the Algorithmic Language Algol 60 において既に、thenの帰結節には直接ifを置くことができないという規則になっていて、回避されている(「PLY (Python Lex-Yacc) で作る Algol 60 処理系」の最後の部分を見よ)。Javaは構文規則を複雑にして解決している。
実のところ、この規則による問題は、dangling else というよりは、もっと他のところにある。
まず、構文があいまい(多義的: ambiguous)であることそのものよりも、上で示した例のように、インデントに騙されて誤解しうる、という可読性の低下、というよりは誤読性を高くしうる、という点が問題である。
これは実際のところelseとは関係なくC言語ではフィラー的なthenキーワードが無い点と <空文> の存在によって(セミコロンの有無・要不要が、構文規則全体を見渡していまいちアドホックである、という遠因もある)、次のような誤読性の高いコードが書けることは有名であろう。

if (a == 1);  // この行の行末のコロンにより空文があるため、ここでif文は終了
    x = 2;    // なので、この行は無条件に実行される

このような問題の解決は、すでにいくつも行われている。以下、それを紹介する。どれにも、制御構造のifの帰結節はブロックとする、という考えが基本としてある。
まず、if文の規則を、次のようなものにする、というものである。

<IF文>     = "if" "(" <式> ")" <ブロック> ["else" <ブロック>]

C言語Javaでも、コーディング規則としてこのように書くことを決めている組織なども多い。ただ、この場合、else if が次のようにどんどん深くなってしまう。

if (a == 1) {
    x = 2;
} else {
    if (b == 3) {
        y = 4;
    } else {
        z = 5;
    }
}

なので、コーディング規則ならば else if の場合は例外とする、という例外を入れる。言語仕様の場合は、elseif か elif といったようなキーワードを追加して問題を回避する(昔、後付けで「構造化」したBasicで、ELSEIFの無いものがあり、ひどいプログラムになってしまうものがありました。また else if というフレーズでキーワードとすることも考えられます。昔は結構そういう複数のワードから成るキーワードがありましたが、最近はあまり見ないように思います)。Perlがこのような規則を採用しており、Go言語も採用した。このような規則にすると、if文の先頭の部分の条件式を囲む ( ) が必要なくなるので、Go言語では取っ払われている。
Rubyなどでは、ブロックではなく、最初から複数の文が書ける、という規則にしている。その場合最後に endif(ないし単に end)のようなキーワードが必要になる(さらにRubyではブロックをもっと別のものとして活用している)。
Pythonではオフサイドルール(wikipedia:オフサイドルール)によりブロックの範囲が決まる。