私は、Cのグローバル変数がextern
キーワードを持っていることを知っています。 extern
変数とは何ですか?宣言はどのようなものですか?その範囲は何ですか?
これは、ソースファイル間で変数を共有することに関連していますが、それはどのように正確に機能しますか? extern
はどこで使いますか?
extern
変数は、他の翻訳単位で定義されている変数の宣言です(sbiのおかげでありがとう)。つまり、変数の記憶域は別のファイルに割り当てられています。
2つの.c
-ファイルtest1.c
とtest2.c
があるとします。 int test1_var;
でグローバル変数test1.c
を定義し、test2.c
でこの変数にアクセスしたい場合は、extern int test1_var;
でtest2.c
を使用する必要があります。
完全なサンプル:
$ cat test1.c
int test1_var = 5;
$ cat test2.c
#include <stdio.h>
extern int test1_var;
int main(void) {
printf("test1_var = %d\n", test1_var);
return 0;
}
$ gcc test1.c test2.c -o test
$ ./test
test1_var = 5
Externは、変数自体が別の翻訳単位にあることを宣言するために使用するキーワードです。
したがって、翻訳単位で変数を使用してから別の変数からその変数にアクセスし、次に2番目の変数でexternとして宣言すると、シンボルはリンカによって解決されます。
あなたがそれをexternとして宣言しないならば、あなたは同じと命名されたが全く関係していない2つの変数と変数の複数の定義のエラーを得るでしょう。
私はあなたがコンパイラにする約束としてextern変数を考えるのが好きです。
Externに遭遇したとき、コンパイラはその型を見つけることができるだけであり、それが "存在する"場所ではないので、参照を解決することはできません。
あなたはそれを言っています、「私を信頼します。リンク時に、この参照は解決可能になるでしょう」
externは、この変数のメモリが他の場所で宣言されていることをユーザーに信頼するようにコンパイラーに指示するため、メモリーの割り当て/チェックを試みません。
したがって、外部参照を持つファイルをコンパイルすることはできますが、そのメモリがどこかで宣言されていない場合はリンクできません。
グローバル変数やライブラリには便利ですが、リンカがcheckを入力しないため危険です。
extern
を追加すると、変数 definition が変数 宣言 に変わります。宣言と定義の違いについては、 このスレッド を参照してください。
Externの正しい解釈はあなたがコンパイラに何かを言うということです。今は存在しないにもかかわらず、宣言された変数はリンカによって何らかの方法で(通常は別のオブジェクト(ファイル)内に)検出されることをコンパイラに伝えます。 extern宣言があってもなくても、リンカーはすべてを見つけ出してまとめることができる幸運な人です。
C言語では、example.cというファイル内の変数にローカルスコープが与えられます。コンパイラは、その変数の定義が同じファイルexample.c内にあることを想定していますが、見つからない場合はエラーをスローします。一方、関数はデフォルトでグローバルスコープを持ちます。そのため、コンパイラに "look dude ...この関数の定義がここにあるかもしれない"と明示的に言及する必要はありません。その宣言を含んでいるファイルを含む関数のためには十分です(あなたが実際にヘッダファイルと呼ぶファイル)。たとえば、次の2つのファイルを考えてください。
example.c
#include<stdio.h>
extern int a;
main(){
printf("The value of a is <%d>\n",a);
}
example1.c
int a = 5;
では、次のコマンドを使用して、2つのファイルをまとめてコンパイルするとします。
ステップ1)cc -o ex example.c example1.cステップ2)./ ex
次のような出力が得られます。aの値は<5>です。
externキーワードは、グローバル変数として識別するために変数とともに使用されます。
また、externキーワードを使用して宣言された変数は他のファイルで宣言/定義されていますが、どのファイルでも使用できることを表します。
GCC ELF Linuxの実装
main.c
:
#include <stdio.h>
int not_extern_int = 1;
extern int extern_int;
void main() {
printf("%d\n", not_extern_int);
printf("%d\n", extern_int);
}
コンパイルと逆コンパイル
gcc -c main.c
readelf -s main.o
出力内容:
Num: Value Size Type Bind Vis Ndx Name
9: 0000000000000000 4 OBJECT GLOBAL DEFAULT 3 not_extern_int
12: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND extern_int
System V ABIアップデートELF仕様 "シンボルテーブル"の章では、次のことを説明しています。
SHN_UNDEFこのセクションテーブルインデックスは、シンボルが未定義であることを意味します。リンクエディタがこのオブジェクトファイルを指定されたシンボルを定義する別のファイルと組み合わせると、このファイルのシンボルへの参照は実際の定義にリンクされます。
これは基本的にC標準がextern
変数に与える振る舞いです。
これ以降、最終プログラムを作成するのはリンカの仕事ですが、extern
情報はすでにソースコードからオブジェクトファイルに抽出されています。
GCC 4.8でテスト済み。
C++ 17インライン変数
C++ 17では、外部変数の代わりにインライン変数を使用することをお勧めします。これは、それらが使用するのが簡単で(ヘッダーで一度だけ定義できます)、より強力であるため(constexprをサポート)参照: CおよびC++で 'const static'とはどういう意味ですか?
extern
は、単に変数が他の場所(例えば、他のファイル)で定義されていることを意味します。
extern
を使用すると、プログラムのあるモジュールから、プログラムの別のモジュールで宣言されているグローバル変数または関数にアクセスできます。通常、ヘッダファイルでextern変数が宣言されています。
プログラムから変数や関数にアクセスしたくない場合は、static
を使用します。これは、この変数または関数をこのモジュールの外部では使用できないことをコンパイラーに指示します。
declare | define | initialize |
----------------------------------
extern int a; yes no no
-------------
int a = 2019; yes yes yes
-------------
int a; yes yes no
-------------
宣言ではメモリは割り当てられません(メモリ割り当てのために変数を定義する必要があります)が、定義では割り当てられません。他の答えは本当に素晴らしいので、これはexternキーワードに関するもう一つの単純な見方です。
まず、extern
キーワードは変数の定義には使用されません。むしろそれは変数を宣言するために使用されます。 extern
はデータ型ではなくストレージクラスだと言えます。
extern
は他のCファイルや外部コンポーネントにこの変数が既にどこかで定義されていることを知らせるために使われます。例:あなたがライブラリを構築しているならば、ライブラリ自体のどこかにグローバル変数を強制的に定義する必要はありません。ライブラリは直接コンパイルされますが、ファイルをリンクしている間に定義をチェックします。
1つのfirst.c
ファイルが別のsecond.c
ファイル内のグローバルパラメータへのフルアクセスを持つことができるようにextern
が使用されます。
extern
は、first.c
ファイルまたはfirst.c
が含むヘッダーファイルのいずれかで宣言できます。
Xc8では、誤って、あるファイルでint
、別のファイルでchar
のように宣言することがあるので、各ファイルで同じ型として変数を宣言することに注意する必要があります。これは変数の破損につながる可能性があります。
この問題は、約15年前にマイクロチップフォーラムでエレガントに解決されました。*「http:www.htsoft.com」//フォーラムを参照してください。 0 /ページ/ 0#18766 "
しかし、このリンクはもはや機能しないようです...
だから私はすぐにそれを説明しようと思います。 global.hというファイルを作成します。
その中で次のように宣言しています
#ifdef MAIN_C
#define GLOBAL
/* #warning COMPILING MAIN.C */
#else
#define GLOBAL extern
#endif
GLOBAL unsigned char testing_mode; // example var used in several C files
今main.cファイルに
#define MAIN_C 1
#include "global.h"
#undef MAIN_C
つまり、main.cでは、変数はunsigned char
として宣言されます。
Global.hを含む他のファイルでは、そのファイルのextern として宣言されています。
extern unsigned char testing_mode;
しかし、それはunsigned char
として正しく宣言されます。
古いフォーラム投稿はおそらくこれをもう少し明確に説明しています。しかし、あるファイルで変数を宣言し、それを別のファイルで別の型として宣言することを可能にするコンパイラーを使用する場合、これは本当の潜在的なgotcha
です。これに関連した問題は、testing_modeを別のファイルのintとして宣言したとしたら、それは16ビットのvarだと考え、ramの他の部分を上書きして、別の変数を破壊する可能性があります。デバッグが難しい!
ヘッダーファイルにオブジェクトの外部参照または実際の実装を含めるために使用する非常に短いソリューション。オブジェクトを実際に含むファイルは、#define GLOBAL_FOO_IMPLEMENTATION
を実行します。次に、このファイルに新しいオブジェクトを追加すると、定義をコピーして貼り付けなくても、そのファイルに表示されます。
このパターンを複数のファイルで使用します。そのため、可能な限り自己完結型に保つために、各ヘッダーで単一のGLOBALマクロを再利用するだけです。私のヘッダーは次のようになります。
//file foo_globals.h
#pragma once
#include "foo.h" //contains definition of foo
#ifdef GLOBAL
#undef GLOBAL
#endif
#ifdef GLOBAL_FOO_IMPLEMENTATION
#define GLOBAL
#else
#define GLOBAL extern
#endif
GLOBAL Foo foo1;
GLOBAL Foo foo2;
//file main.cpp
#define GLOBAL_FOO_IMPLEMENTATION
#include "foo_globals.h"
//file uses_extern_foo.cpp
#include "foo_globals.h