回答:
使用extern
することは、ビルドしているプログラムがリンクされた複数のソースファイルで構成されている場合にのみfile1.c
意味があります。たとえば、ソースファイルで定義された変数の一部は、などの他のソースファイルで参照する必要がありますfile2.c
。
変数は、変数が存在することをコンパイラーに通知されたときに宣言されます(これはその型です)。その時点では、変数のストレージは割り当てられません。
変数は、コンパイラーが変数のストレージを割り当てるときに定義されます。
変数は複数回宣言できます(1回で十分です)。特定のスコープ内で1回だけ定義できます。変数定義も宣言ですが、すべての変数宣言が定義であるとは限りません。
グローバル変数を宣言および定義するためのクリーンで信頼できる方法は、ヘッダーファイルを使用して変数のextern
宣言を含めることです。
ヘッダーは、変数を定義する1つのソースファイルと、変数を参照するすべてのソースファイルに含まれます。プログラムごとに、1つのソースファイル(および1つのソースファイルのみ)が変数を定義します。同様に、1つのヘッダーファイル(および1つのヘッダーファイルのみ)が変数を宣言する必要があります。ヘッダーファイルは重要です。独立したTU(翻訳単位-ソースファイルと考える)間のクロスチェックを可能にし、一貫性を保証します。
それを行う他の方法がありますが、この方法はシンプルで信頼性があります。それはによって証明されfile3.h
、file1.c
そしてfile2.c
:
extern int global_variable; /* Declaration of the variable */
#include "file3.h" /* Declaration made available here */
#include "prog1.h" /* Function declarations */
/* Variable defined here */
int global_variable = 37; /* Definition checked against declaration */
int increment(void) { return global_variable++; }
#include "file3.h"
#include "prog1.h"
#include <stdio.h>
void use_it(void)
{
printf("Global variable: %d\n", global_variable++);
}
これは、グローバル変数を宣言および定義する最良の方法です。
次の2つのファイルで、ソースが完成しますprog1
。
示されている完全なプログラムは関数を使用しているため、関数宣言が組み込まれています。C99とC11はどちらも、使用する前に関数を宣言または定義する必要があります(正当な理由により、C90ではそうではありませんでした)。extern
一貫性を保つために、ヘッダー内の関数宣言の前にキーワードを使用しています—ヘッダーextern
内の変数宣言の前に一致させるためです。多くの人extern
は関数宣言の前で使用しないことを好みます。コンパイラは気にしません—そして最終的には、少なくともソースファイル内で一貫している限り、私も気にしません。
extern void use_it(void);
extern int increment(void);
#include "file3.h"
#include "prog1.h"
#include <stdio.h>
int main(void)
{
use_it();
global_variable += 19;
use_it();
printf("Increment: %d\n", increment());
return 0;
}
prog1
用途prog1.c
、file1.c
、file2.c
、file3.h
とprog1.h
。ファイルprog1.mk
はprog1
専用のメイクファイルです。これはmake
、ミレニアムの変わり目以降に作成されたほとんどのバージョンで動作します。特にGNU Makeに関連付けられているわけではありません。
# Minimal makefile for prog1
PROGRAM = prog1
FILES.c = prog1.c file1.c file2.c
FILES.h = prog1.h file3.h
FILES.o = ${FILES.c:.c=.o}
CC = gcc
SFLAGS = -std=c11
GFLAGS = -g
OFLAGS = -O3
WFLAG1 = -Wall
WFLAG2 = -Wextra
WFLAG3 = -Werror
WFLAG4 = -Wstrict-prototypes
WFLAG5 = -Wmissing-prototypes
WFLAGS = ${WFLAG1} ${WFLAG2} ${WFLAG3} ${WFLAG4} ${WFLAG5}
UFLAGS = # Set on command line only
CFLAGS = ${SFLAGS} ${GFLAGS} ${OFLAGS} ${WFLAGS} ${UFLAGS}
LDFLAGS =
LDLIBS =
all: ${PROGRAM}
${PROGRAM}: ${FILES.o}
${CC} -o $@ ${CFLAGS} ${FILES.o} ${LDFLAGS} ${LDLIBS}
prog1.o: ${FILES.h}
file1.o: ${FILES.h}
file2.o: ${FILES.h}
# If it exists, prog1.dSYM is a directory on macOS DEBRIS = a.out core *~ *.dSYM RM_FR = rm -fr
clean:
${RM_FR} ${FILES.o} ${PROGRAM} ${DEBRIS}
ルールは専門家だけが違反するべきであり、正当な理由がある場合のみ:
ヘッダーファイルにはextern
、変数の宣言のみが含まれstatic
ます。変数の定義は含まれていません
。
特定の変数について、1つのヘッダーファイルのみがそれを宣言します(SPOT — Single Point of Truth)。
ソースファイルextern
に変数の宣言が含まれることはありません。ソースファイルには常に、それらを宣言する(唯一の)ヘッダーが含まれています。
特定の変数について、1つのソースファイルで変数を定義し、できれば初期化することもできます。(明示的にゼロに初期化する必要はありませんが、プログラム内の特定のグローバル変数の初期化定義は1つしか存在できないため、害はなく、ある程度の効果があります)。
変数を定義するソースファイルには、定義と宣言の整合性を確保するためのヘッダーも含まれています。
関数は、を使用して変数を宣言する必要はありませんextern
。
可能な限りグローバル変数を避けてください—代わりに関数を使用してください。
この回答のソースコードとテキストは、GitHubのsrc / so-0143-3204サブディレクトリにある私のSOQ(Stack Overflow Questions)リポジトリで入手できます。
あなたが経験豊富なCプログラマーでなければ、ここを読むのをやめることができます(おそらくそうすべきです)。
一部の(実際には多くの)Cコンパイラーを使用すると、変数の「共通」定義と呼ばれるものを回避できます。ここで「共通」とは、(おそらく名前が付けられた)COMMONブロックを使用して、ソースファイル間で変数を共有するためにFortranで使用される手法を指します。ここで何が起こるかは、いくつかのファイルのそれぞれが変数の暫定的な定義を提供することです。初期化された定義を提供するファイルが1つしかない限り、さまざまなファイルが変数の共通の単一の定義を共有することになります。
#include "prog2.h"
long l; /* Do not do this in portable code */
void inc(void) { l++; }
#include "prog2.h"
long l; /* Do not do this in portable code */
void dec(void) { l--; }
#include "prog2.h"
#include <stdio.h>
long l = 9; /* Do not do this in portable code */
void put(void) { printf("l = %ld\n", l); }
この手法は、C標準の文字および「1つの定義ルール」に準拠していません。これは、公式には未定義の動作です。
外部リンケージのある識別子が使用されていますが、プログラムにその識別子の外部定義が1つだけ存在しないか、識別子が使用されておらず、識別子に複数の外部定義が存在します(6.9)。
外部定義は、関数(インライン定義以外)またはオブジェクトの定義である外部宣言です。外部リンケージで宣言された識別子が式で使用される場合(
sizeof
または_Alignof
結果が整数定数であるor 演算子のオペランドの一部として以外)、プログラム全体のどこかに、識別子の外部定義が1つだけ存在します。それ以外の場合は、1つだけでなければなりません。161)161)したがって、外部リンケージで宣言された識別子が式で使用されない場合、その外部定義は必要ありません。
ただし、C規格では、情報を付属文書JにCommon extensionsの 1つとして記載しています。
キーワードexternの明示的な使用の有無にかかわらず、オブジェクトの識別子には複数の外部定義がある場合があります。定義が一致しない場合、または複数の定義が初期化されている場合、動作は未定義です(6.9.2)。
この手法は常にサポートされているわけではないため、特にコードを移植可能にする必要がある場合は、この手法を使用しないことをお勧めします。この手法を使用すると、意図しないタイプのパンニングが発生する可能性もあります。
上記のファイルの1つがl
としてではdouble
なくとして宣言されているlong
場合、Cのタイプ安全でないリンカーはおそらく不一致を検出しません。64ビットlong
とを搭載したマシンを使用している場合はdouble
、警告も表示されません。32ビットlong
と64 ビットのマシンでdouble
は、おそらく異なるサイズについて警告が表示されます。リンカーは、Fortranプログラムが一般的なブロックの最大サイズを取るのとまったく同じように、最大サイズを使用します。
2020-05-07にリリースされたGCC 10.1.0は、デフォルトのコンパイルオプションをに変更することに注意してください。つまり-fno-common
、デフォルトでは、上記のコードは、デフォルトをオーバーライド-fcommon
(または属性などを使用)しない限りリンクしません。リンクを参照してください)。
次の2つのファイルで、ソースが完成しますprog2
。
extern void dec(void);
extern void put(void);
extern void inc(void);
#include "prog2.h"
#include <stdio.h>
int main(void)
{
inc();
put();
dec();
put();
dec();
put();
}
prog2
用途prog2.c
、file10.c
、file11.c
、file12.c
、prog2.h
。ここでのコメントや、同様の質問に対する私の回答で述べたように、グローバル変数に複数の定義を使用すると、未定義の動作(J.2、§6.9)につながります。起こり得ることの1つは、プログラムが期待どおりに動作することです。そしてJ.5.11は、おおよそ、「あなたはあなたが値するよりも幸運であるかもしれない」と言っています。ただし、extern変数の複数の定義に依存するプログラム(明示的な 'extern'キーワードの有無にかかわらず)は、厳密に準拠するプログラムではなく、どこでも機能することは保証されていません。同等に、それはそれ自体を表示する場合と表示しない場合があるバグを含んでいます。
もちろん、これらのガイドラインに違反する可能性のある方法はたくさんあります。時折、ガイドラインに違反する正当な理由があるかもしれませんが、そのような状況は非常にまれです。
c int some_var; /* Do not do this in a header!!! */
注1:ヘッダーがextern
キーワードなしで変数を定義する場合、ヘッダーを含む各ファイルは変数の暫定的な定義を作成します。前述したように、これは多くの場合機能しますが、C標準では機能することを保証していません。
c int some_var = 13; /* Only one source file in a program can use this */
注2:ヘッダーが変数を定義および初期化する場合、特定のプログラムの1つのソースファイルのみがヘッダーを使用できます。ヘッダーは主に情報を共有するためのものであるため、一度しか使用できないヘッダーを作成するのは少しばかげています。
c static int hidden_global = 3; /* Each source file gets its own copy */
注3:ヘッダーが静的変数を定義している場合(初期化あり、またはなし)、各ソースファイルは、独自のプライベートバージョンの「グローバル」変数になります。
たとえば、変数が実際に複雑な配列である場合、これによりコードが極端に重複する可能性があります。それは、ごくまれに、何らかの効果を達成するための賢明な方法になることがありますが、それは非常に珍しいことです。
最初に示したヘッダー手法を使用します。どこでも確実に動作します。特に、を宣言するヘッダーglobal_variable
は、それを定義するヘッダーを含め、それを使用するすべてのファイルに含まれていることに注意してください。これにより、すべてが首尾一貫したものになります。
関数の宣言と定義でも同様の問題が発生します—同様のルールが適用されます。しかし、問題は具体的には変数に関するものだったので、変数に対する答えのみを保持しました。
経験豊富なCプログラマでない場合は、おそらくここを読むのをやめるべきです。
後期メジャー追加
ここで説明する「ヘッダー内の宣言、ソース内の定義」メカニズムについて時々(そして正当に)提起される懸念の1つは、同期が維持される2つのファイル、つまりヘッダーとソースがあることです。これには通常、マクロを使用してヘッダーが二重の役割を果たすようにすることができるという観察が続きます。通常は変数を宣言しますが、ヘッダーが含まれる前に特定のマクロが設定されると、代わりに変数を定義します。
もう1つの懸念は、変数をいくつかの「メインプログラム」のそれぞれで定義する必要があることです。これは通常、誤った懸念事項です。Cソースファイルを導入して変数を定義し、各プログラムで生成されたオブジェクトファイルをリンクするだけです。
典型的なスキームは次のように機能しますfile3.h
:に示されている元のグローバル変数を使用します。
#ifdef DEFINE_VARIABLES
#define EXTERN /* nothing */
#else
#define EXTERN extern
#endif /* DEFINE_VARIABLES */
EXTERN int global_variable;
#define DEFINE_VARIABLES
#include "file3a.h" /* Variable defined - but not initialized */
#include "prog3.h"
int increment(void) { return global_variable++; }
#include "file3a.h"
#include "prog3.h"
#include <stdio.h>
void use_it(void)
{
printf("Global variable: %d\n", global_variable++);
}
次の2つのファイルで、ソースが完成しますprog3
。
extern void use_it(void);
extern int increment(void);
#include "file3a.h"
#include "prog3.h"
#include <stdio.h>
int main(void)
{
use_it();
global_variable += 19;
use_it();
printf("Increment: %d\n", increment());
return 0;
}
prog3
用途prog3.c
、file1a.c
、file2a.c
、file3a.h
、prog3.h
。示されているこのスキームの問題は、グローバル変数の初期化を提供しないことです。C99またはC11とマクロの可変引数リストを使用すると、初期化もサポートするマクロを定義できます。(C89でマクロの可変引数リストがサポートされていないため、任意に長い初期化子を処理する簡単な方法はありません。)
#ifdef DEFINE_VARIABLES
#define EXTERN /* nothing */
#define INITIALIZER(...) = __VA_ARGS__
#else
#define EXTERN extern
#define INITIALIZER(...) /* nothing */
#endif /* DEFINE_VARIABLES */
EXTERN int global_variable INITIALIZER(37);
EXTERN struct { int a; int b; } oddball_struct INITIALIZER({ 41, 43 });
#if
および#else
ブロックの内容を逆にし、Denis Kniazhevによって識別されたバグを修正
#define DEFINE_VARIABLES
#include "file3b.h" /* Variables now defined and initialized */
#include "prog4.h"
int increment(void) { return global_variable++; }
int oddball_value(void) { return oddball_struct.a + oddball_struct.b; }
#include "file3b.h"
#include "prog4.h"
#include <stdio.h>
void use_them(void)
{
printf("Global variable: %d\n", global_variable++);
oddball_struct.a += global_variable;
oddball_struct.b -= global_variable / 2;
}
明らかに、oddball構造のコードは、通常記述するコードではありませんが、ポイントを示しています。INITIALIZER
is の2番目の呼び出しの最初の引数はis { 41
で、残りの引数(この例では単数)は43 }
です。マクロの可変引数リストに対するC99または同様のサポートがない場合、コンマを含める必要がある初期化子は非常に問題があります。
Denis Kniazhevによるfile3b.h
(の代わりにfileba.h
)
正しいヘッダーが含まれる
次の2つのファイルで、ソースが完成しますprog4
。
extern int increment(void);
extern int oddball_value(void);
extern void use_them(void);
#include "file3b.h"
#include "prog4.h"
#include <stdio.h>
int main(void)
{
use_them();
global_variable += 19;
use_them();
printf("Increment: %d\n", increment());
printf("Oddball: %d\n", oddball_value());
return 0;
}
prog4
用途prog4.c
、file1b.c
、file2b.c
、prog4.h
、file3b.h
。型定義(列挙型、構造体型または共用体型、または一般的にtypedef)が問題を引き起こさないように、ヘッダーは再包含から保護する必要があります。標準的な手法は、次のようなヘッダーガードでヘッダーの本体をラップすることです。
#ifndef FILE3B_H_INCLUDED
#define FILE3B_H_INCLUDED
...contents of header...
#endif /* FILE3B_H_INCLUDED */
ヘッダーが間接的に2回含まれる可能性があります。たとえば、表示されていないタイプ定義にがfile4b.h
含まfile3b.h
れていて、file1b.c
ヘッダーfile4b.h
との両方を使用する必要file3b.h
がある場合は、解決するためのさらに難しい問題があります。明らかに、ヘッダーリストを改訂して、だけを含めることができますfile4b.h
。ただし、内部の依存関係を認識していない可能性があります。理想的には、コードは引き続き機能します。
さらに、定義を生成するためにインクルードfile4b.h
する前にインクルードする可能性があるため、トリッキーになりますfile3b.h
が、通常のヘッダーガードでfile3b.h
はヘッダーが再インクルードされません。
したがって、file3b.h
宣言の場合は最大1回、定義の場合は最大1回の本体を含める必要がありますが、両方を単一の変換単位(TU —ソースファイルとそれが使用するヘッダーの組み合わせ)に含める必要がある場合があります。
しかし、それはあまりにも無理のない制約の下で行うことができます。新しいファイル名のセットを紹介しましょう:
external.h
EXTERNマクロ定義など
file1c.h
(特に、タイプを定義することstruct oddball
の、タイプoddball_struct
)。
file2c.h
グローバル変数を定義または宣言します。
file3c.c
グローバル変数を定義します。
file4c.c
これは単にグローバル変数を使用します。
file5c.c
これは、グローバル変数を宣言して定義できることを示しています。
file6c.c
これは、グローバル変数を定義してから(試行して)宣言できることを示しています。
これらの例では、ヘッダーfile5c.c
をfile6c.c
直接file2c.h
数回インクルードしていますが、それがメカニズムが機能することを示す最も簡単な方法です。これは、ヘッダーが間接的に2回含まれていた場合も安全であることを意味します。
これが機能するための制限は次のとおりです。
グローバル変数を定義または宣言するヘッダー自体は、タイプを定義しない場合があります。
変数を定義する必要があるヘッダーをインクルードする直前に、マクロDEFINE_VARIABLESを定義します。
変数を定義または宣言するヘッダーには、様式化された内容があります。
#ifdef DEFINE_VARIABLES
#define EXTERN /* nothing */
#define INITIALIZE(...) = __VA_ARGS__
#else
#define EXTERN extern
#define INITIALIZE(...) /* nothing */
#endif /* DEFINE_VARIABLES */
#ifndef FILE1C_H_INCLUDED
#define FILE1C_H_INCLUDED
struct oddball
{
int a;
int b;
};
extern void use_them(void);
extern int increment(void);
extern int oddball_value(void);
#endif /* FILE1C_H_INCLUDED */
/* Standard prologue */
#if defined(DEFINE_VARIABLES) && !defined(FILE2C_H_DEFINITIONS)
#undef FILE2C_H_INCLUDED
#endif
#ifndef FILE2C_H_INCLUDED
#define FILE2C_H_INCLUDED
#include "external.h" /* Support macros EXTERN, INITIALIZE */
#include "file1c.h" /* Type definition for struct oddball */
#if !defined(DEFINE_VARIABLES) || !defined(FILE2C_H_DEFINITIONS)
/* Global variable declarations / definitions */
EXTERN int global_variable INITIALIZE(37);
EXTERN struct oddball oddball_struct INITIALIZE({ 41, 43 });
#endif /* !DEFINE_VARIABLES || !FILE2C_H_DEFINITIONS */
/* Standard epilogue */
#ifdef DEFINE_VARIABLES
#define FILE2C_H_DEFINITIONS
#endif /* DEFINE_VARIABLES */
#endif /* FILE2C_H_INCLUDED */
#define DEFINE_VARIABLES
#include "file2c.h" /* Variables now defined and initialized */
int increment(void) { return global_variable++; }
int oddball_value(void) { return oddball_struct.a + oddball_struct.b; }
#include "file2c.h"
#include <stdio.h>
void use_them(void)
{
printf("Global variable: %d\n", global_variable++);
oddball_struct.a += global_variable;
oddball_struct.b -= global_variable / 2;
}
#include "file2c.h" /* Declare variables */
#define DEFINE_VARIABLES
#include "file2c.h" /* Variables now defined and initialized */
int increment(void) { return global_variable++; }
int oddball_value(void) { return oddball_struct.a + oddball_struct.b; }
#define DEFINE_VARIABLES
#include "file2c.h" /* Variables now defined and initialized */
#include "file2c.h" /* Declare variables */
int increment(void) { return global_variable++; }
int oddball_value(void) { return oddball_struct.a + oddball_struct.b; }
次のソースファイルはprog5
、prog6
およびのソースを完成させます(メインプログラムを提供します)prog7
。
#include "file2c.h"
#include <stdio.h>
int main(void)
{
use_them();
global_variable += 19;
use_them();
printf("Increment: %d\n", increment());
printf("Oddball: %d\n", oddball_value());
return 0;
}
prog5
用途prog5.c
、file3c.c
、file4c.c
、file1c.h
、file2c.h
、external.h
。
prog6
用途prog5.c
、file5c.c
、file4c.c
、file1c.h
、file2c.h
、external.h
。
prog7
用途prog5.c
、file6c.c
、file4c.c
、file1c.h
、file2c.h
、external.h
。
このスキームはほとんどの問題を回避します。変数などを定義するヘッダー(などfile2c.h
)が、変数を定義する別のヘッダー(たとえばfile7c.h
)に含まれている場合にのみ、問題が発生します。「それをしないでください」以外にそれを回避する簡単な方法はありません。
次のように修正file2c.h
することで、問題を部分的に回避できますfile2d.h
。
/* Standard prologue */
#if defined(DEFINE_VARIABLES) && !defined(FILE2D_H_DEFINITIONS)
#undef FILE2D_H_INCLUDED
#endif
#ifndef FILE2D_H_INCLUDED
#define FILE2D_H_INCLUDED
#include "external.h" /* Support macros EXTERN, INITIALIZE */
#include "file1c.h" /* Type definition for struct oddball */
#if !defined(DEFINE_VARIABLES) || !defined(FILE2D_H_DEFINITIONS)
/* Global variable declarations / definitions */
EXTERN int global_variable INITIALIZE(37);
EXTERN struct oddball oddball_struct INITIALIZE({ 41, 43 });
#endif /* !DEFINE_VARIABLES || !FILE2D_H_DEFINITIONS */
/* Standard epilogue */
#ifdef DEFINE_VARIABLES
#define FILE2D_H_DEFINITIONS
#undef DEFINE_VARIABLES
#endif /* DEFINE_VARIABLES */
#endif /* FILE2D_H_INCLUDED */
問題は「ヘッダーを含める必要があります#undef DEFINE_VARIABLES
か?」ヘッダーからそれを省略し、定義呼び出しを#define
andでラップする場合#undef
:
#define DEFINE_VARIABLES
#include "file2c.h"
#undef DEFINE_VARIABLES
ソースコードで(ヘッダーがの値を変更しないようにDEFINE_VARIABLES
)、クリーンアップする必要があります。余分な行を書くことを覚えておかなければならないのは、単に迷惑です。代替案は次のとおりです。
#define HEADER_DEFINING_VARIABLES "file2c.h"
#include "externdef.h"
#if defined(HEADER_DEFINING_VARIABLES)
#define DEFINE_VARIABLES
#include HEADER_DEFINING_VARIABLES
#undef DEFINE_VARIABLES
#undef HEADER_DEFINING_VARIABLES
#endif /* HEADER_DEFINING_VARIABLES */
これは、(使用してコンボリューション少しを取得しますが、安全であるように思われfile2d.h
ていないと、し#undef DEFINE_VARIABLES
でfile2d.h
)。
/* Declare variables */
#include "file2d.h"
/* Define variables */
#define HEADER_DEFINING_VARIABLES "file2d.h"
#include "externdef.h"
/* Declare variables - again */
#include "file2d.h"
/* Define variables - again */
#define HEADER_DEFINING_VARIABLES "file2d.h"
#include "externdef.h"
int increment(void) { return global_variable++; }
int oddball_value(void) { return oddball_struct.a + oddball_struct.b; }
/* Standard prologue */
#if defined(DEFINE_VARIABLES) && !defined(FILE8C_H_DEFINITIONS)
#undef FILE8C_H_INCLUDED
#endif
#ifndef FILE8C_H_INCLUDED
#define FILE8C_H_INCLUDED
#include "external.h" /* Support macros EXTERN, INITIALIZE */
#include "file2d.h" /* struct oddball */
#if !defined(DEFINE_VARIABLES) || !defined(FILE8C_H_DEFINITIONS)
/* Global variable declarations / definitions */
EXTERN struct oddball another INITIALIZE({ 14, 34 });
#endif /* !DEFINE_VARIABLES || !FILE8C_H_DEFINITIONS */
/* Standard epilogue */
#ifdef DEFINE_VARIABLES
#define FILE8C_H_DEFINITIONS
#endif /* DEFINE_VARIABLES */
#endif /* FILE8C_H_INCLUDED */
/* Define variables */
#define HEADER_DEFINING_VARIABLES "file2d.h"
#include "externdef.h"
/* Define variables */
#define HEADER_DEFINING_VARIABLES "file8c.h"
#include "externdef.h"
int increment(void) { return global_variable++; }
int oddball_value(void) { return oddball_struct.a + oddball_struct.b; }
次の2つのファイルはソースを完了prog8
してprog9
:
#include "file2d.h"
#include <stdio.h>
int main(void)
{
use_them();
global_variable += 19;
use_them();
printf("Increment: %d\n", increment());
printf("Oddball: %d\n", oddball_value());
return 0;
}
#include "file2d.h"
#include <stdio.h>
void use_them(void)
{
printf("Global variable: %d\n", global_variable++);
oddball_struct.a += global_variable;
oddball_struct.b -= global_variable / 2;
}
prog8
用途prog8.c
、file7c.c
、file9c.c
。
prog9
用途prog8.c
、file8c.c
、file9c.c
。
ただし、実際に問題が発生する可能性は比較的低いです。特に、標準的なアドバイスを
この博覧会は何かを見逃していますか?
告白:ここで概説する「重複コードの回避」スキームは、問題が私が取り組んでいる(しかし所有していない)コードに影響を与えるために開発されたものであり、回答の最初の部分で概説されているスキームの微妙な懸念事項です。ただし、元のスキームでは、変数の定義と宣言を同期させるために変更する場所が2つしか残されていません。これは、コードベース全体に外部変数宣言が散在していることを大きく前進させます(合計で数千のファイルがある場合に問題になります)。 。ただし、ファイル内のコードfileNc.[ch]
(プラスexternal.h
とexterndef.h
)は、それを機能させることができることを示しています。明らかに、ヘッダーファイルを定義および宣言する変数の標準化されたテンプレートを提供するヘッダージェネレータースクリプトを作成することは難しくありません。
注意:これらは、わずかに面白くするのに十分なだけのコードが含まれているおもちゃのプログラムです。例の中には削除できる繰り返しがありますが、教育学的説明を単純化するためではありません。(例:差prog5.c
とがprog8.c
含まれているヘッダーの1つの名前であり、そのようにコードを再編成することが可能である。main()
機能が繰り返されなかったが、それはより多くのことが明らかになったよりも隠すことになります。)
foo.h
:()):#define FOO_INITIALIZER { 1, 2, 3, 4, 5 }
配列の初期化子を定義し、配列enum { FOO_SIZE = sizeof((int [])FOO_INITIALIZER) / sizeof(((int [])FOO_INITIALIZER)[0]) };
のサイズを取得extern int foo[];
し、配列を宣言します。明らかに、定義はちょうどint foo[FOO_SIZE] = FOO_INITIALIZER;
であるべきですが、サイズは実際に定義に含まれる必要はありません。これにより、整数定数が得られますFOO_SIZE
。
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 int test1_var;
てint test1_var;
も、リンカー(gcc 5.4.0)は引き続き成功します。それで、extern
この場合本当に必要ですか?
Externは、変数自体が別の翻訳単位にあることを宣言するために使用するキーワードです。
したがって、翻訳単位で変数を使用し、別の変数からその変数にアクセスすることを決定できます。次に、2番目の変数でそれをexternとして宣言すると、シンボルはリンカーによって解決されます。
externとして宣言しないと、同じ名前でまったく関連のない2つの変数と、変数の複数の定義のエラーが発生します。
externは、この変数のメモリが他の場所で宣言されていることを信頼するようにコンパイラに指示するため、メモリの割り当て/チェックを試みません。
したがって、externへの参照を持つファイルをコンパイルできますが、そのメモリがどこかで宣言されていない場合はリンクできません。
グローバル変数とライブラリに役立ちますが、リンカが型チェックを行わないため危険です。
externの正しい解釈は、コンパイラに何かを伝えることです。コンパイラーに、現在存在しないにもかかわらず、宣言された変数がリンカーによって(通常は別のオブジェクト(ファイル)で)検出されることを伝えます。リンカーは、extern宣言があったかどうかに関係なく、すべてを見つけてまとめる幸運な人になります。
Cでは、ファイル内の変数はexample.cにローカルスコープが指定されていると言います。コンパイラは、変数が同じファイルexample.c内にその定義を持っていると想定し、同じものが見つからない場合はエラーをスローします。一方、関数にはデフォルトでグローバルスコープがあります。したがって、コンパイラに明示的に言及する必要はありません。その宣言を含むファイルを含む関数で十分です(実際にヘッダーファイルを呼び出すファイル)。たとえば、次の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>です
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
システムV ABIアップデートELF仕様「シンボルテーブル」の章は説明します:
SHN_UNDEFこのセクションテーブルインデックスは、シンボルが未定義であることを意味します。リンクエディターがこのオブジェクトファイルを指定されたシンボルを定義する別のオブジェクトファイルと組み合わせると、このファイルのシンボルへの参照は実際の定義にリンクされます。
これは基本的にC標準がextern
変数に与える動作です。
これからは、最終的なプログラムを作成するのはリンカの仕事ですが、extern
情報はすでにソースコードからオブジェクトファイルに抽出されています。
GCC 4.8でテスト済み。
C ++ 17インライン変数
C ++ 17では、extern変数の代わりにインライン変数を使用することをお勧めします。これは、変数が簡単に使用でき(ヘッダーで一度だけ定義でき)、より強力な(constexprをサポートする)ためです。参照:CおよびC ++での「const static」の意味は?
readelf
またはの出力をnm
確認することは役立ちますが、を使用する方法の基本を説明していませんextern
。また、実際の定義で最初のプログラムを完了していません。あなたのコードはも使用していませんnotExtern
。命名法の問題もあります。notExtern
ここではで宣言するのextern
ではなく、ここで定義しますが、これらの翻訳単位に適切な宣言が含まれている場合、他のソースファイルからアクセスできる外部変数です(extern int notExtern;
!
notExtern
が醜かったので直しました。命名法について、もっと良い名前があるかどうか教えてください。もちろんそれは実際のプログラムには良い名前ではありませんが、ここでは教訓的な役割によく適合していると思います。
global_def
に関して、ここで定義された変数、およびextern_ref
他のモジュールで定義された変数はどうですか?それらは適切に明確な対称性を持っているでしょうか?それでもint extern_ref = 57;
、それが定義されているファイルで、またはそれに似たものになるので、名前はあまり理想的ではありませんが、単一のソースファイルのコンテキスト内では、それは合理的な選択です。持つextern int global_def;
ヘッダにはできるだけ多くの問題ではない、それは私には思えます。もちろん、あなた次第です。
extern
プログラムの1つのモジュールが、プログラムの別のモジュールで宣言されたグローバル変数または関数にアクセスできるようにします。通常、ヘッダーファイルでextern変数が宣言されています。
プログラムが変数または関数にアクセスしないようにする場合は、を使用static
して、この変数または関数をこのモジュールの外では使用できないことをコンパイラーに指示します。
まず、extern
キーワードは変数の定義には使用されません。むしろ、変数を宣言するために使用されます。私が言うことができるextern
ストレージ・クラスではなく、データ型です。
extern
この変数がすでにどこかに定義されていることを他のCファイルまたは外部コンポーネントに知らせるために使用されます。例:ライブラリを構築している場合、グローバル変数をライブラリ自体のどこかに強制的に定義する必要はありません。ライブラリは直接コンパイルされますが、ファイルをリンクするときに、定義をチェックします。
extern
を使用して、1つのfirst.c
ファイルが別のsecond.c
ファイルのグローバルパラメータに完全にアクセスできるようにします。
extern
内で宣言することができfirst.c
、ファイルやヘッダファイルのいずれにもfirst.c
含まれています。
extern
宣言は内ではなくヘッダー内にある必要があることに注意してくださいfirst.c
。これにより、型が変更されると、宣言も変更されます。また、変数が宣言されているヘッダーsecond.c
は、定義が宣言と一致していることを確認するために、に含める必要があります。ヘッダーの宣言は、すべてをまとめる接着剤です。これにより、ファイルを個別にコンパイルできますが、グローバル変数のタイプの一貫したビューが確実に得られます。
xc8では、各ファイルで変数を同じ型として宣言する場合に注意する必要があります。誤って、int
あるファイルで何かを宣言しchar
、別のファイルで発言を宣言する必要があります。これにより、変数が破損する可能性があります。
この問題は、約15年前にマイクロチップフォーラムでエレガントに解決されました/ *「http:www.htsoft.com」を参照してください / / 「forum / all / showflat.php / Cat / 0 / Number / 18766 / an / 0 / page / 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 unsigned char testing_mode;
しかし、それはとして正しく宣言されますunsigned char
。
古いフォーラム投稿はおそらくこれをもう少し明確に説明しました。しかし、これは、gotcha
あるファイルで変数を宣言してから、別のファイルで別の型としてexternを宣言できるコンパイラーを使用する場合の真の可能性です。これに関連する問題は、testing_modeを別のファイルでintとして宣言した場合、16ビット変数であると見なし、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