AFAIK、ファイルスコープ内の変数または関数の宣言には、デフォルトで外部リンケージがあります。static
「内部リンケージがある」という意味で、extern
「外部リンケージがある」ではなく、「他の場所で定義されている可能性があります」という意味です。
もしそうなら、なぜ私たちはextern
キーワードが必要ですか?int foo;
つまり、とextern int foo;
(ファイルスコープ)の違いは何ですか?
AFAIK、ファイルスコープ内の変数または関数の宣言には、デフォルトで外部リンケージがあります。static
「内部リンケージがある」という意味で、extern
「外部リンケージがある」ではなく、「他の場所で定義されている可能性があります」という意味です。
もしそうなら、なぜ私たちはextern
キーワードが必要ですか?int foo;
つまり、とextern int foo;
(ファイルスコープ)の違いは何ですか?
キーワードは、extern
主に変数宣言に使用されます。関数を前方宣言する場合、キーワードはオプションです。
このキーワードにより、コンパイラーはグローバル変数の前方宣言と変数の定義を区別できます。
extern double xyz; // Declares xyz without defining it
この宣言を単独で保持xyz
してからコードで使用すると、リンクフェーズ中に「未定義のシンボル」エラーがトリガーされます。
double xyz; // Declares and defines xyz
この宣言をヘッダーファイルに保持し、複数のC / C ++ファイルから使用すると、リンクフェーズ中に「複数の定義」エラーが発生します。
解決策はextern
、ヘッダーで使用することであり、 1つのCまたはC++ファイルでexternを使用しないことです。
例として、次のプログラムをコンパイルします:(cc -c program.cまたは同等のものを使用)
extern char bogus[0x12345678] ;
ここで、「extern」キーワードを削除して、再度コンパイルします。
char bogus[0x12345678] ="1";
2つのオブジェクトでobjdump(または同等のもの)を実行します。
externキーワードがないと、スペースが実際に割り当てられていることがわかります。
extern
すると、「偽の」もの全体が参照にすぎません。あなたはコンパイラにこう言っています:「どこかにあるに違いない、char bogus[xxx]
それを直してください!」char bogus[xxx]
です。そのスペースをください!」と言います。紛らわしいのは、オブジェクトへの実際のメモリの割り当てがリンク時まで延期されることです。コンパイラはオブジェクトにレコードを追加するだけで、オブジェクトを割り当てる必要がある(または割り当てない)ことをリンカに通知します。すべての場合において、コンパイラは少なくともオブジェクトの名前(およびサイズ)を追加するので、リンカ/ローダーはそれを修正できます。
C99標準
他の人が言ったことを繰り返しますが、C99N1256ドラフトを引用して解釈します。
最初に、外部リンケージがファイルスコープ6.2.2/5「識別子のリンケージ」のデフォルトであるというあなたの主張を確認します。
オブジェクトの識別子の宣言にファイルスコープがあり、ストレージクラス指定子がない場合、そのリンケージは外部にあります。
混乱のポイントはextern
、リンケージを変更するだけでなく、オブジェクト宣言が定義であるかどうかを変更することです。6.9 / 5の「外部定義」では、外部定義は1つしか存在できないと記述されているため、これは重要です。
外部定義は、関数(インライン定義以外)またはオブジェクトの定義でもある外部宣言です。外部リンケージで宣言された識別子が式で使用される場合(結果が整数定数であるsizeof演算子のオペランドの一部として以外)、プログラム全体のどこかに、識別子の外部定義が1つだけ存在する必要があります。それ以外の場合は、1つだけにする必要があります。
ここで、「外部定義」は文法スニペットによって定義されます。
translation-unit: external-declaration
つまり、「ファイルスコープ」の最上位宣言を意味します。
次に、6.9.2 / 2「外部オブジェクト定義」は次のように述べています(オブジェクトは「変数のデータ」を意味します)。
初期化子がなく、ストレージクラス指定子がないか、ストレージクラス指定子が静的であるファイルスコープを持つオブジェクトの識別子の宣言は、暫定的な定義を構成します。翻訳ユニットに識別子の1つ以上の仮定義が含まれ、翻訳ユニットにその識別子の外部定義が含まれていない場合、動作は、翻訳ユニットにその識別子のファイルスコープ宣言が含まれている場合とまったく同じです。 0に等しい初期化子を使用して、変換単位の終わりの。
それで:
extern int i;
ストレージクラス指定子があるため、は定義ではありません。extern
でも:
int i;
ストレージクラス指定子がないため、暫定的な定義です。また、の外部宣言がこれ以上ない場合はi
、0に等しいイニシャライザを= 0
暗黙的に追加できます。
int i = 0;
したがってint i;
、異なるファイルに複数ある場合、リンカは理論的には複数の定義で爆発するはずです。
ただし、GCC 4.8は準拠していません。拡張機能として、httpsint i;
://stackoverflow.com/a/3692486/895245で説明されているように、異なるファイル間で複数のファイルを使用できます。
これは共通の記号を使用してELFに実装されており、この拡張機能は非常に一般的であるため、J.5.11 /5の標準で言及されています。一般的な拡張機能>複数の外部定義:
キーワードexternの明示的な使用の有無にかかわらず、オブジェクトの識別子には複数の外部定義が存在する場合があります。定義が一致しない場合、または複数が初期化されている場合、動作は未定義です(6.9.2)。
効果があるもう1つの場所extern
は、ブロックスコープ宣言です。「ローカル変数とレジスタ変数をexternとして宣言できますか? 」を参照してください。
オブジェクト宣言の初期化子がある場合、extern
効果はありません。
extern int i = 0;
等しい
int i = 0;
どちらも定義です。
関数の場合、extern
効果がないようです。暫定的な定義の類似した概念がないため、C関数に対するexternキーワードの効果。
変数を定義できるのは1回だけです。
複数のファイルが同じ変数を使用する場合、変数は各ファイルで冗長に宣言する必要があります。単純な「intfoo;」を実行する場合 重複定義エラーが発生します。重複定義エラーを回避するには、「extern」を使用します。Externは、コンパイラに「この変数は存在しますが、作成しないでください。別の場所で定義されています」と言っているようなものです。
Cのビルドプロセスは「スマート」ではありません。変数が存在するかどうかを確認するためにすべてのファイルを検索するわけではありません。変数が現在のファイルに存在することを明示的に指定する必要がありますが、同時にそれを2回作成することは避けてください。
同じファイルであっても、ビルドプロセスはあまり賢くありません。上から下に移動し、使用ポイントの下で定義されている場合は関数名を認識しないため、上で宣言する必要があります。