違いを理解するには、C の仮定義と呼ばれる概念に慣れる必要があります。C 標準を引用すると、次のようになります。
C11、ドラフト、§6.9.2、外部オブジェクト定義
ファイル スコープを持ち、初期化子がなく、ストレージ クラス指定子がないか、またはストレージ クラス指定子が static であるオブジェクトの識別子の宣言は、暫定的な定義を構成します。翻訳単位に識別子の暫定的な定義が 1 つ以上含まれており、翻訳単位にその識別子の外部定義が含まれていない場合、動作は、翻訳単位にその識別子のファイル スコープ宣言が含まれているかのように、複合型は次のようになります。 0 に等しい初期化子を使用して、翻訳単位の末尾を指定します。
最初のスニペットにあるのは、 の暫定的な定義にすぎませんi。1 つのオブジェクトに対して必要な数の仮の定義を設定できます (ただし、定義は 1 つしか許可されません)。
int i; // tentative definition
int i; // tentative definition
int i; // tentative definition
int main(void) {
return 0;
}
有効です。
ここでiは、外部リンクがあり、仮定義されています。が同じ翻訳単位のどこかで定義されている場合i、それが の実際の定義になりiます。翻訳単位に isの他の定義iが見つからない場合、これは次のように定義されているかのように完全な定義になります。
int i = 0;
int main(void) {
return 0;
}
しかし、2 番目のスニペットint i;は暫定的な定義ではありません。仮定義できるのは、外部結合を持つオブジェクトだけです。2 番目のスニペットでは、宣言extern int i;はi、外部リンケージを使用して別の場所で定義されていると述べています。しかし、次の行int i;はi、リンケージなしで定義されていることを示しています (ローカル自動変数にはリンケージがありません。これは暫定的な定義ではありません) 。したがって、 の定義に矛盾がありますi。したがって、最初の 1 つのスニペットは問題ありませんが、2 番目のスニペットはそうではありません。