HN 日本語サマリー

← 一覧へ戻る
プログラミング

C89における、決して解決されない曖昧さ

An ambiguity in c89 which will never be fixed (sebsite.pw)

57 pointsby runningmike19 コメント

要約

この記事では、C89(またはC90)標準における暗黙的な関数宣言の解釈に関する曖昧さについて論じています。GCCとClangという主要なコンパイラ間でこの曖昧さの解釈が異なり、C99でこの機能が削除されたため、将来的に解決されることはありません。著者は、この曖昧さが引き起こす興味深いエッジケースを例を挙げて説明し、C89の使用を控えるよう呼びかけています。

全文翻訳

C89における、決して解決されない曖昧さ 2026-08-10 C89(/C90)標準において、GCCとClangが解釈を異にする曖昧な記述を見つけました。これは暗黙的な関数宣言の動作に関するもので、C99で削除されたため、この問題が明確化されることはありませんでした。 知らない方のために説明すると、C89にはクールな「機能」があります。それは、存在しない関数を呼び出そうとした場合に、エラーを出すのではなく、その関数がパラメータがなくintを返す関数として暗黙的に宣言されるというものです。具体的には、呼び出し式中の関数が括弧のない識別子で、かつスコープ内に存在しない場合、それは extern int () という関数としてスコープに挿入されます。(このことに関する面白い点は、一見無駄に見える括弧が意味論に影響を与えるということです。f() は f をスコープに挿入しますが、(f)() はそうしません。) さて、この機能を使った面白いエッジケースを紹介します。しかし、それにたどり着く前に、簡単なことから始めましょう。これは、デクラレータがそれ自体を宣言する宣言です。 int f(int [sizeof(f())]); これは、1つのパラメータを持つ関数を宣言します。そのパラメータは配列(ポインタに減衰する)です。デクラレータが完了した後、識別子はスコープに挿入されます。そのため、配列デクラレータ内で f() が呼び出されるとき、それはまだスコープ内にないため、int () として宣言されます。その後、デクラレータが完了し、f は互換性のある型 int (int *) として再宣言されます。 ここで、宣言に1文字追加して変更してみましょう。 int f(int f[sizeof(f())]); さて、これは一体どういうことでしょうか。これは合法なのでしょうか? いや、本当に、合法なのでしょうか? ネタバレすると、Clang はこれを問題なくコンパイルしますが、GCC はエラーを出します。 したがって、コンパイラが意味論で意見が分かれるときはいつでも、聖典を参照します。ANSI X.3-159-1989、3.3.2.2 関数呼び出し:関数呼び出しの括弧で囲まれた引数リストの前に来る式が識別子のみで構成されており、この識別子に対して可視な宣言がない場合、その識別子は、関数呼び出しを含む最も内側のブロックで、あたかも extern int identifier(); という宣言が現れたかのように、暗黙的に宣言されます。 鍵となるフレーズは「最も内側のブロック」です。標準では、これが何を意味するのかを詳しく説明していません。直感的には、これはブロックスコープ(つまり、複合文、ほぼ)を意味すると考えるでしょう。その場合、コードは合法であるはずです。しかし、宣言はファイルスコープ、つまりどのブロックの外にも現れる可能性があります。ファイルスコープでの暗黙的な宣言は、明示的に禁止されておらず、GCCとClangの両方がそれを許可しています。 したがって、別の解釈としては、それが実際に最も内側のスコープを意味するというものです。その場合、f() は関数プロトタイプスコープ、つまり関数デクラレータ内で宣言されることになります。もしそうなら、パラメータ f は無効な再宣言(int () を int として再宣言)となるため、コードは違法であるはずです。これが正しい解釈ではないかもしれないことのヒントの1つは、暗黙的な宣言が extern int () の形式を持つと指定されていることです。extern 指定子は関数プロトタイプスコープ内では許可されないため、ここでは除外されているのかもしれません。「最も内側のブロック」は実際には最も内側のブロックスコープまたはファイルスコープを意味し、したがってコードは本当に合法なのかもしれません。 ここに別の例があります。これは、動作の乖離が暗黙的な宣言がどのスコープに入るかにかかっていることを確認します。ここでは、Clang はエラーを出し、GCC は成功します(前の例とは逆です)。 int main(void) { int f(int [sizeof(x())]); int x; } また、GCC では、関数プロトタイプスコープでの暗黙的な宣言はポインタに減衰しないことにも注意してください。たとえ関数プロトタイプスコープでの関数型が通常ありえないとしてもです。GCC は、以下のコンパイル時にエラーを出します。なぜなら、f は int (*)() として再宣言されるからです。 int f(int [sizeof(f())], int f()); これは、Clang の動作の方がより「正しい」可能性を示唆する、もう1つのヒントです。 問題のある動作は27年以上前に標準から削除されたため、意図された解釈が何であったかを知ることは決してできません。 無関係な脚注 P.S. お願いですから、C89を使うのをやめてください。お願いします。C11は15年以上前に最終化されました。十分な時間があったはずです。大丈夫だと約束します。 「しかし、私は25年前に倒産した会社のニッチなマイクロプロセッサのためにコードを書いており、この唯一のプロプライエタリなC89コンパイラしかサポートされておらず、それ以外のものを使おうとするとマシンが自爆します」 もし、他に選択肢がないという正当な理由があるなら、あなたは自分が誰であるかを知っているでしょう。ほとんどの人には言い訳がありません。