externを使用してソースファイル間で変数を共有するにはどうすればよいですか?


987

Cのグローバル変数にはexternキーワードがある場合があることを知っています。extern変数とは何ですか?宣言はどのようなものですか?その範囲は何ですか?

これはソースファイル間で変数を共有することに関連していますが、それはどのように正確に機能しますか?どこで使用しexternますか?

回答:


1751

使用externすることは、ビルドしているプログラムがリンクされた複数のソースファイルで構成されている場合にのみfile1.c意味があります。たとえば、ソースファイルで定義された変数の一部は、などの他のソースファイルで参照する必要がありますfile2.c

変数の定義と変数の宣言の違い理解することが重要です

  • 変数は、変数が存在することをコンパイラーに通知されたときに宣言されます(これはその型です)。その時点では、変数のストレージは割り当てられません。

  • 変数は、コンパイラーが変数のストレージを割り当てるときに定義されます。

変数は複数回宣言できます(1回で十分です)。特定のスコープ内で1回だけ定義できます。変数定義も宣言ですが、すべての変数宣言が定義であるとは限りません。

グローバル変数を宣言および定義する最良の方法

グローバル変数を宣言および定義するためのクリーンで信頼できる方法は、ヘッダーファイルを使用して変数のextern 宣言を含めることです。

ヘッダーは、変数を定義する1つのソースファイルと、変数を参照するすべてのソースファイルに含まれます。プログラムごとに、1つのソースファイル(および1つのソースファイルのみ)が変数を定義します。同様に、1つのヘッダーファイル(および1つのヘッダーファイルのみ)が変数を宣言する必要があります。ヘッダーファイルは重要です。独立したTU(翻訳単位-ソースファイルと考える)間のクロスチェックを可能にし、一貫性を保証します。

それを行う他の方法がありますが、この方法はシンプルで信頼性があります。それはによって証明されfile3.hfile1.cそしてfile2.c

file3.h

extern int global_variable;  /* Declaration of the variable */

file1.c

#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++; }

file2.c

#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は関数宣言の前で使用しないことを好みます。コンパイラは気にしません—そして最終的には、少なくともソースファイル内で一貫している限り、私も気にしません。

prog1.h

extern void use_it(void);
extern int increment(void);

prog1.c

#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.cfile1.cfile2.cfile3.hprog1.h

ファイルprog1.mkprog1専用のメイクファイルです。これはmake、ミレニアムの変わり目以降に作成されたほとんどのバージョンで動作します。特にGNU Makeに関連付けられているわけではありません。

prog1.mk

# 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つしかない限り、さまざまなファイルが変数の共通の単一の定義を共有することになります。

file10.c

#include "prog2.h"

long l; /* Do not do this in portable code */ 

void inc(void) { l++; }

file11.c

#include "prog2.h"

long l; /* Do not do this in portable code */ 

void dec(void) { l--; }

file12.c

#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つの定義ルール」に準拠していません。これは、公式には未定義の動作です。

J.2未定義の動作

外部リンケージのある識別子が使用されていますが、プログラムにその識別子の外部定義が1つだけ存在しないか、識別子が使用されておらず、識別子に複数の外部定義が存在します(6.9)。

§6.9外部定義¶5

外部定義は、関数(インライン定義以外)またはオブジェクトの定義である外部宣言です。外部リンケージで宣言された識別子が式で使用される場合(sizeofまたは_Alignof結果が整数定数であるor 演算子のオペランドの一部として以外)、プログラム全体のどこかに、識別子の外部定義が1つだけ存在します。それ以外の場合は、1つだけでなければなりません。161)

161)したがって、外部リンケージで宣言された識別子が式で使用されない場合、その外部定義は必要ありません。

ただし、C規格では、情報を付属文書JにCommon extensionsの 1つとして記載しています。

J.5.11複数の外部定義

キーワード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

prog2.h

extern void dec(void);
extern void put(void);
extern void inc(void);

prog2.c

#include "prog2.h"
#include <stdio.h>

int main(void)
{
    inc();
    put();
    dec();
    put();
    dec();
    put();
}
  • prog2用途prog2.cfile10.cfile11.cfile12.cprog2.h

警告

ここでのコメントや、同様の質問に対する私の回答で述べたように、グローバル変数に複数の定義を使用すると、未定義の動作(J.2、§6.9)につながります。起こり得ることの1つは、プログラムが期待どおりに動作することです。そしてJ.5.11は、おおよそ、「あなたはあなたが値するよりも幸運であるかもしれない」と言っています。ただし、extern変数の複数の定義に依存するプログラム(明示的な 'extern'キーワードの有無にかかわらず)は、厳密に準拠するプログラムではなく、どこでも機能することは保証されていません。同等に、それはそれ自体を表示する場合と表示しない場合があるバグを含んでいます。

ガイドライン違反

もちろん、これらのガイドラインに違反する可能性のある方法はたくさんあります。時折、ガイドラインに違反する正当な理由があるかもしれませんが、そのような状況は非常にまれです。

faulty_header.h

c int some_var; /* Do not do this in a header!!! */

注1:ヘッダーがexternキーワードなしで変数を定義する場合、ヘッダーを含む各ファイルは変数の暫定的な定義を作成します。前述したように、これは多くの場合機能しますが、C標準では機能することを保証していません。

broken_header.h

c int some_var = 13; /* Only one source file in a program can use this */

注2:ヘッダーが変数を定義および初期化する場合、特定のプログラムの1つのソースファイルのみがヘッダーを使用できます。ヘッダーは主に情報を共有するためのものであるため、一度しか使用できないヘッダーを作成するのは少しばかげています。

seldom_correct.h

c static int hidden_global = 3; /* Each source file gets its own copy */

注3:ヘッダーが静的変数を定義している場合(初期化あり、またはなし)、各ソースファイルは、独自のプライベートバージョンの「グローバル」変数になります。

たとえば、変数が実際に複雑な配列である場合、これによりコードが極端に重複する可能性があります。それは、ごくまれに、何らかの効果を達成するための賢明な方法になることがありますが、それは非常に珍しいことです。


概要

最初に示したヘッダー手法を使用します。どこでも確実に動作します。特に、を宣言するヘッダーglobal_variableは、それを定義するヘッダーを含め、それを使用するすべてのファイルに含まれていることに注意してください。これにより、すべてが首尾一貫したものになります。

関数の宣言と定義でも同様の問題が発生します—同様のルールが適用されます。しかし、問題は具体的には変数に関するものだったので、変数に対する答えのみを保持しました。

元の回答の終わり

経験豊富なCプログラマでない場合は、おそらくここを読むのをやめるべきです。


後期メジャー追加

コードの重複を避ける

ここで説明する「ヘッダー内の宣言、ソース内の定義」メカニズムについて時々(そして正当に)提起される懸念の1つは、同期が維持される2つのファイル、つまりヘッダーとソースがあることです。これには通常、マクロを使用してヘッダーが二重の役割を果たすようにすることができるという観察が続きます。通常は変数を宣言しますが、ヘッダーが含まれる前に特定のマクロが設定されると、代わりに変数を定義します。

もう1つの懸念は、変数をいくつかの「メインプログラム」のそれぞれで定義する必要があることです。これは通常、誤った懸念事項です。Cソースファイルを導入して変数を定義し、各プログラムで生成されたオブジェクトファイルをリンクするだけです。

典型的なスキームは次のように機能しますfile3.h:に示されている元のグローバル変数を使用します。

file3a.h

#ifdef DEFINE_VARIABLES
#define EXTERN /* nothing */
#else
#define EXTERN extern
#endif /* DEFINE_VARIABLES */

EXTERN int global_variable;

file1a.c

#define DEFINE_VARIABLES
#include "file3a.h"  /* Variable defined - but not initialized */
#include "prog3.h"

int increment(void) { return global_variable++; }

file2a.c

#include "file3a.h"
#include "prog3.h"
#include <stdio.h>

void use_it(void)
{
    printf("Global variable: %d\n", global_variable++);
}

次の2つのファイルで、ソースが完成しますprog3

prog3.h

extern void use_it(void);
extern int increment(void);

prog3.c

#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.cfile1a.cfile2a.cfile3a.hprog3.h

変数の初期化

示されているこのスキームの問題は、グローバル変数の初期化を提供しないことです。C99またはC11とマクロの可変引数リストを使用すると、初期化もサポートするマクロを定義できます。(C89でマクロの可変引数リストがサポートされていないため、任意に長い初期化子を処理する簡単な方法はありません。)

file3b.h

#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によって識別されたバグを修正

file1b.c

#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; }

file2b.c

#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構造のコードは、通常記述するコードではありませんが、ポイントを示しています。INITIALIZERis の2番目の呼び出しの最初の引数はis { 41で、残りの引数(この例では単数)は43 }です。マクロの可変引数リストに対するC99または同様のサポートがない場合、コンマを含める必要がある初期化子は非常に問題があります。

Denis Kniazhevによるfile3b.h(の代わりにfileba.h) 正しいヘッダーが含まれる


次の2つのファイルで、ソースが完成しますprog4

prog4.h

extern int increment(void);
extern int oddball_value(void);
extern void use_them(void);

prog4.c

#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.cfile1b.cfile2b.cprog4.hfile3b.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.cfile6c.c直接file2c.h数回インクルードしていますが、それがメカニズムが機能することを示す最も簡単な方法です。これは、ヘッダーが間接的に2回含まれていた場合も安全であることを意味します。

これが機能するための制限は次のとおりです。

  1. グローバル変数を定義または宣言するヘッダー自体は、タイプを定義しない場合があります。

  2. 変数を定義する必要があるヘッダーをインクルードする直前に、マクロDEFINE_VARIABLESを定義します。

  3. 変数を定義または宣言するヘッダーには、様式化された内容があります。

外部.h


#ifdef DEFINE_VARIABLES
#define EXTERN              /* nothing */
#define INITIALIZE(...)     = __VA_ARGS__
#else
#define EXTERN              extern
#define INITIALIZE(...)     /* nothing */
#endif /* DEFINE_VARIABLES */

file1c.h

#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 */

file2c.h


/* 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 */

file3c.c

#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; }

file4c.c

#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;
}

file5c.c


#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; }

file6c.c


#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; }


次のソースファイルはprog5prog6およびのソースを完成させます(メインプログラムを提供します)prog7

prog5.c

#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.cfile3c.cfile4c.cfile1c.hfile2c.hexternal.h

  • prog6用途prog5.cfile5c.cfile4c.cfile1c.hfile2c.hexternal.h

  • prog7用途prog5.cfile6c.cfile4c.cfile1c.hfile2c.hexternal.h


このスキームはほとんどの問題を回避します。変数などを定義するヘッダー(などfile2c.h)が、変数を定義する別のヘッダー(たとえばfile7c.h)に含まれている場合にのみ、問題が発生します。「それをしないでください」以外にそれを回避する簡単な方法はありません。

次のように修正file2c.hすることで、問題を部分的に回避できますfile2d.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か?」ヘッダーからそれを省略し、定義呼び出しを#defineandでラップする場合#undef

#define DEFINE_VARIABLES
#include "file2c.h"
#undef DEFINE_VARIABLES

ソースコードで(ヘッダーがの値を変更しないようにDEFINE_VARIABLES)、クリーンアップする必要があります。余分な行を書くことを覚えておかなければならないのは、単に迷惑です。代替案は次のとおりです。

#define HEADER_DEFINING_VARIABLES "file2c.h"
#include "externdef.h"

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_VARIABLESfile2d.h)。

file7c.c

/* 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; }

file8c.h

/* 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 */

file8c.c

/* 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

prog8.c

#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;
}

file9c.c

#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.cfile7c.cfile9c.c

  • prog9用途prog8.cfile8c.cfile9c.c


ただし、実際に問題が発生する可能性は比較的低いです。特に、標準的なアドバイスを

グローバル変数を避ける


この博覧会は何かを見逃していますか?

告白:ここで概説する「重複コードの回避」スキームは、問題が私が取り組んでいる(しかし所有していない)コードに影響を与えるために開発されたものであり、回答の最初の部分で概説されているスキームの微妙な懸念事項です。ただし、元のスキームでは、変数の定義と宣言を同期させるために変更する場所が2つしか残されていません。これは、コードベース全体に外部変数宣言が散在していることを大きく前進させます(合計で数千のファイルがある場合に問題になります)。 。ただし、ファイル内のコードfileNc.[ch](プラスexternal.hexterndef.h)は、それを機能させることができることを示しています。明らかに、ヘッダーファイルを定義および宣言する変数の標準化されたテンプレートを提供するヘッダージェネレータースクリプトを作成することは難しくありません。

注意:これらは、わずかに面白くするのに十分なだけのコードが含まれているおもちゃのプログラムです。例の中には削除できる繰り返しがありますが、教育学的説明を単純化するためではありません。(例:差prog5.cとがprog8.c含まれているヘッダーの1つの名前であり、そのようにコードを再編成することが可能である。main()機能が繰り返されなかったが、それはより多くのことが明らかになったよりも隠すことになります。)


3
@litb:一般的な定義については、付録J.5.11を参照してください。これは一般的な拡張機能です。
ジョナサンレフラー

3
@litb:そして、私はそれが回避されるべきであることに同意します-それが「グローバル変数を定義するためにそれほど良くない方法」のセクションにある理由です。
ジョナサンレフラー、

3
実際、これは一般的な拡張ですが、プログラムがそれに依存するための未定義の動作です。これがC独自のルールで許可されていると言っているのかどうかは、はっきりしていませんでした。これは一般的な拡張機能であり、コードを移植可能にする必要がある場合はそれを回避するためのものだと言っています。だから私は疑いなくあなたを賛成することができます。本当に素晴らしい答え私見:)
ヨハネス・シャウブ-litb

19
トップに立ち止まると、シンプルなことが簡単になります。さらに読み進めていくと、微妙なニュアンス、複雑さ、および詳細が扱われます。経験の浅いCプログラマー、または既にこの主題を知っているCプログラマーのために、2つの「早期停止ポイント」を追加しました。答えがわかっている場合は、すべてを読む必要はありません(ただし、技術的な欠陥を見つけた場合はお知らせください)。
ジョナサンレフラー2014

4
@supercat:C99配列リテラルを使用して、配列サイズの列挙値を取得できます(例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
ジョナサンレフラー2014

125

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

21
「疑似定義」はありません。それは宣言です。
sbi 2009

3
上記の例で、をに変更しextern int test1_var;int test1_var;も、リンカー(gcc 5.4.0)は引き続き成功します。それで、externこの場合本当に必要ですか?
radiohead 2018年

2
@radiohead:私の回答externは、ドロップすることはよく機能する一般的な拡張機能であり、特にGCCで機能するという情報があります(ただし、GCCはそれをサポートする唯一のコンパイラではありません。Unixシステムで一般的です)。-あなたは「J.5.11」またはセクション「それほど良い方法」私の答えで(それは私が知るために見ることができているし、それを説明する(またはそうしようとする)は、その近くのテキストの長さ)。
ジョナサンレフラー

extern宣言は確かに別の翻訳単位で定義する必要はありません(通常はそうではありません)。実際、宣言と定義は同じものにすることができます。
モニカの

40

Externは、変数自体が別の翻訳単位にあることを宣言するために使用するキーワードです。

したがって、翻訳単位で変数を使用し、別の変数からその変数にアクセスすることを決定できます。次に、2番目の変数でそれをexternとして宣言すると、シンボルはリンカーによって解決されます。

externとして宣言しないと、同じ名前でまったく関連のない2つの変数と、変数の複数の定義のエラーが発生します。


5
つまり、externが使用されている変換ユニットは、この変数やそのタイプなどを認識しているため、基礎となるロジックのソースコードでそれを使用できますが、変数を割り当てません。別の変換ユニットがそれを行います。両方の変換ユニットが変数を正常に宣言する場合、変数には2つの物理的な場所があり、コンパイルされたコード内の関連する「間違った」参照と、リンカーの結果のあいまいさがあります。
mjv 2009

26

私は、extern変数をコンパイラーに約束するものとして考えたいと思います。

externに遭遇した場合、コンパイラーはそのタイプを見つけることができるだけであり、「生存」している場所を見つけることができないため、参照を解決できません。

あなたはそれを言っています、「私を信頼してください。リンク時にこの参照は解決可能です」。


より一般的には、宣言は名前がリンク時に正確に1つの定義に解決可能であるという約束です。externは、定義せずに変数を宣言します。
リーライアン

18

externは、この変数のメモリが他の場所で宣言されていることを信頼するようにコンパイラに指示するため、メモリの割り当て/チェックを試みません。

したがって、externへの参照を持つファイルをコンパイルできますが、そのメモリがどこかで宣言されていない場合はリンクできません。

グローバル変数とライブラリに役立ちますが、リンカが型チェックを行わないため危険です。


メモリは宣言されていません。詳細については、この質問の回答をご覧ください:stackoverflow.com/questions/1410563
sbi 2009

15

を追加するexternと、変数定義が変数宣言になります。宣言と定義の違いについては、このスレッドを参照してください。


int fooextern int foo(ファイルスコープ)の違いは何ですか?どちらも宣言ですよね。

@ user14284:すべての定義も宣言であるという意味でのみ、どちらも宣言です。しかし、私はこれの説明にリンクしました。(「宣言と定義の違いについては、このスレッドを参照してください。」)リンクをたどって読んでみませんか?
sbi

14
                 declare | define   | initialize |
                ----------------------------------

extern int a;    yes          no           no
-------------
int a = 2019;    yes          yes          yes
-------------
int a;           yes          yes          no
-------------

宣言はメモリを割り当てません(変数はメモリ割り当てのために定義する必要があります)が、定義は割り当てます。他の答えは本当に素晴らしいので、これはexternキーワードのもう1つの単純なビューです。


11

externの正しい解釈は、コンパイラに何かを伝えることです。コンパイラーに、現在存在しないにもかかわらず、宣言された変数がリンカーによって(通常は別のオブジェクト(ファイル)で)検出されることを伝えます。リンカーは、extern宣言があったかどうかに関係なく、すべてを見つけてまとめる幸運な人になります。


8

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>です


8

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」の意味は?


3
それは私の投票ではないので、わかりません。しかし、私は意見を公言します。readelfまたはの出力をnm確認することは役立ちますが、を使用する方法の基本を説明していませんextern。また、実際の定義で最初のプログラムを完了していません。あなたのコードはも使用していませんnotExtern。命名法の問題もあります。notExternここではで宣言するのexternではなく、ここで定義しますが、これらの翻訳単位に適切な宣言が含まれている場合、他のソースファイルからアクセスできる外部変数です(extern int notExtern;
ジョナサンレフラー、2015

1
@JonathanLefflerフィードバックをありがとう!標準の動作と使用法の推奨事項は既に他の回答で行われているので、何が起こっているのかを理解するのに本当に役立つので、実装を少し示すことにしました。使うのnotExternが醜かったので直しました。命名法について、もっと良い名前があるかどうか教えてください。もちろんそれは実際のプログラムには良い名前ではありませんが、ここでは教訓的な役割によく適合していると思います。
Ciro Santilli冠状病毒审查六四事件法轮功

名前global_defに関して、ここで定義された変数、およびextern_ref他のモジュールで定義された変数はどうですか?それらは適切に明確な対称性を持っているでしょうか?それでもint extern_ref = 57;、それが定義されているファイルで、またはそれに似たものになるので、名前はあまり理想的ではありませんが、単一のソースファイルのコンテキスト内では、それは合理的な選択です。持つextern int global_def;ヘッダにはできるだけ多くの問題ではない、それは私には思えます。もちろん、あなた次第です。
Jonathan Leffler、2015

7

externキーワードは、グローバル変数として識別するために変数とともに使用されます。

また、externキーワードを使用して宣言された変数は、他のファイルで宣言/定義されていても、どのファイルでも使用できることを表します。


5

extern プログラムの1つのモジュールが、プログラムの別のモジュールで宣言されたグローバル変数または関数にアクセスできるようにします。通常、ヘッダーファイルでextern変数が宣言されています。

プログラムが変数または関数にアクセスしないようにする場合は、を使用staticして、この変数または関数をこのモジュールの外では使用できないことをコンパイラーに指示します。


5

extern 単に、変数が別の場所(たとえば、別のファイル)で定義されていることを意味します。


4

まず、externキーワードは変数の定義には使用されません。むしろ、変数を宣言するために使用されます。私が言うことができるexternストレージ・クラスではなく、データ型です。

externこの変数がすでにどこかに定義されていることを他のCファイルまたは外部コンポーネントに知らせるために使用されます。例:ライブラリを構築している場合、グローバル変数をライブラリ自体のどこかに強制的に定義する必要はありません。ライブラリは直接コンパイルされますが、ファイルをリンクするときに、定義をチェックします。


3

externを使用して、1つのfirst.cファイルが別のsecond.cファイルのグローバルパラメータに完全にアクセスできるようにします。

extern内で宣言することができfirst.c、ファイルやヘッダファイルのいずれにもfirst.c含まれています。


3
extern宣言は内ではなくヘッダー内にある必要があることに注意してくださいfirst.c。これにより、型が変更されると、宣言も変更されます。また、変数が宣言されているヘッダーsecond.cは、定義が宣言と一致していることを確認するために、に含める必要があります。ヘッダーの宣言は、すべてをまとめる接着剤です。これにより、ファイルを個別にコンパイルできますが、グローバル変数のタイプの一貫したビューが確実に得られます。
Jonathan Leffler、2015

2

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の他の一部を上書きして、別の変数を破壊する可能性があることです。デバッグが難しい!


0

ヘッダーファイルにオブジェクトの外部参照または実際の実装を含めるために使用する非常に短いソリューション。実際にオブジェクトを含んでいるファイルは単にそうし#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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.