1149

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

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

4

18 に答える 18

1993

を使用するexternのは、ビルドしているプログラムがリンクされた複数のソース ファイルで構成されている場合にのみ関係があります。たとえば、ソース ファイルで定義された変数の一部がfile1.cfile2.c.

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

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

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

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

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

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

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

他の方法もありますが、この方法は簡単で確実です。file3.h、 、file1.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;
}
  • prog1prog1.cfile1.cfile2.cfile3.hおよび を使用しprog1.hます。

prog1.mkのみのメイクファイルですprog1。2000 年頃から製造されたほとんどのバージョンで動作します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 の 私のSOQ (Stack Overflow Questions) リポジトリのsrc/so-0143-3204 サブディレクトリにあります。

経験豊富な C プログラマーでない場合は、ここで読むのをやめてもかまいません (おそらくやめるべきです)。

グローバル変数を定義するあまり良い方法ではない

一部の (実際、多くの) C コンパイラでは、変数の「共通」定義と呼ばれるものを使用することもできます。ここでの「共通」は、ソース ファイル間で変数を共有するために Fortran で使用される手法を指し、(おそらく名前が付けられた) COMMON ブロックを使用します。ここで起こることは、多数のファイルのそれぞれが変数の暫定的な定義を提供することです。初期化された定義を提供するファイルが 1 つだけである限り、さまざまなファイルが変数の共通の 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

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

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

ただし、C 規格では、参考資料として Annex J にCommon extensionsの 1 つとして記載されています。

J.5.11 複数の外部定義

キーワード extern の明示的な使用の有無にかかわらず、オブジェクトの識別子には複数の外部定義が存在する場合があります。定義が一致しない場合、または複数が初期化されている場合、動作は未定義です (6.9.2)。

この手法は常にサポートされているわけではないため、特にコードを移植可能にする必要がある場合は、使用しないことをお勧めします。この手法を使用すると、意図しないタイプのパニングが発生する可能性もあります。

上記のファイルの 1 つがではなく として宣言さlれている場合、C の型安全でないリンカはおそらく不一致を見つけられません。64 ビットのマシンを使用している場合は、警告さえ表示されません。32 ビットと 64ビットのマシンでは、異なるサイズに関する警告が表示される可能性があります。Fortran プログラムが共通ブロックの最大サイズを使用するのとまったく同じように、リンカは最大サイズを使用します。doublelonglongdoublelongdouble

2020-05-07 にリリースされた GCC 10.1.0 は、デフォルトのコンパイル オプションを use に変更することに注意して ください-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();
}
  • prog2prog2.cfile10.cfile11.c、 、file12.cを使用しprog2.hます。

警告

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

ガイドライン違反

もちろん、これらのガイドラインを破る方法はたくさんあります。場合によっては、ガイドラインに違反する正当な理由がある場合もありますが、そのような場合は非常にまれです。

faulty_header.h

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

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

壊れたヘッダー.h

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

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

めったに_正しい.h

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

注 3: ヘッダーが (初期化の有無にかかわらず) 静的変数を定義する場合、各ソース ファイルは「グローバル」変数の独自のプライベート バージョンで終了します。

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


概要

最初に示したヘッダー テクニックを使用します。どこでも確実に動作します。特に、 を宣言するヘッダーは、global_variableそれを使用するすべてのファイル (それを定義するファイルを含む) に含まれることに注意してください。これにより、すべての一貫性が保証されます。

関数の宣言と定義でも同様の問題が生じます — 同様の規則が適用されます。しかし、質問は特に変数に関するものだったので、変数のみへの回答を保持しました。

元の回答の終わり

経験豊富な C プログラマーでない場合は、おそらくここで読むのをやめてください。


後期メジャー追加

コードの重複を避ける

ここで説明されている「ヘッダーでの宣言、ソースでの定義」メカニズムについて時々 (そして正当に) 提起される懸念の 1 つは、ヘッダーとソースの 2 つのファイルを同期しておく必要があることです。これは通常、ヘッダーが 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;
}
  • prog3prog3.cfile1a.cfile2a.c、 、file3a.hを使用しprog3.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 });

#ifandブロックの内容を反転し、 Denis Kniazhev#elseによって特定されたバグを修正

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

明らかに、変わった構造のコードは、通常は作成しないものですが、要点を示しています。の 2 番目の呼び出しに対する最初の引数INITIALIZERは で{ 41あり、残りの引数 (この例では単数) は43 }です。マクロの可変引数リストに対する C99 または同様のサポートがない場合、コンマを含める必要がある初期化子は非常に問題になります。

Denis Kniazhevfile3b.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;
}
  • prog4prog4.cfile1b.cfile2b.c、 、prog4.hを使用しfile3b.hます。

ヘッダーガード

型定義 (enum、struct、union 型、または一般的な typedef) が問題を引き起こさないように、すべてのヘッダーを再包含から保護する必要があります。標準的な手法は、次のようなヘッダー ガードでヘッダーの本文をラップすることです。

#ifndef FILE3B_H_INCLUDED
#define FILE3B_H_INCLUDED

...contents of header...

#endif /* FILE3B_H_INCLUDED */

ヘッダーは、間接的に 2 回インクルードされる場合があります。たとえば、 表示されていないタイプ定義をfile4b.hインクルードし、ヘッダーとの両方を使用する必要がある場合、解決すべきさらに難しい問題がいくつかあります。明らかに、ヘッダー リストを修正して. ただし、内部の依存関係に気付いていない可能性があります。コードは、理想的には動作し続ける必要があります。file3b.hfile1b.cfile4b.hfile3b.hfile4b.h

さらに、定義を生成するためにインクルードfile4b.h する前にインクルードする可能性があるため、注意が必要ですが、通常のヘッダー ガードではヘッダーが再インクルードされるのを防ぐことができます。file3b.hfile3b.h

したがって、file3b.h宣言には最大 1 回、定義には最大 1 回の本体を含める必要がありますが、1 つの翻訳単位 (TU — ソース ファイルとそれが使用するヘッダーの組み合わせ) に両方が必要になる場合があります。

変数定義による複数包含

ただし、無理のない制約の下で行うこともできます。新しいファイル名のセットを紹介しましょう:

  • external.hEXTERN マクロ定義など

  • file1c.hタイプを定義します (特に、struct oddballのタイプoddball_struct)。

  • file2c.hグローバル変数を定義または宣言します。

  • file3c.cグローバル変数を定義します。

  • file4c.cこれは単にグローバル変数を使用します。

  • file5c.cこれは、グローバル変数を宣言してから定義できることを示しています。

  • file6c.cこれは、グローバル変数を定義してから (試みて) 宣言できることを示しています。

これらの例でfile5c.cは、ヘッダーを何度かfile6c.c直接インクルードしています が、これがメカニズムが機能していることを示す最も簡単な方法です。file2c.hこれは、ヘッダーが間接的に 2 回インクルードされた場合でも安全であることを意味します。

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

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

  2. 変数を定義するヘッダーを含める直前に、マクロ DEFINE_VARIABLES を定義します。

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

外部.h

/*
** This header must not contain header guards (like <assert.h> must not).
** Each time it is invoked, it redefines the macros EXTERN, INITIALIZE
** based on whether macro DEFINE_VARIABLES is currently defined.
*/
#undef EXTERN
#undef INITIALIZE

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


次のソース ファイルは、 、および のソースを完成させます (メイン プログラムを提供しprog5ますprog6) 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;
}
  • prog5prog5.cfile3c.cfile4c.c、 、file1c.hfile2c.h使用しexternal.hます。

  • prog6prog5.cfile5c.cfile4c.c、 、file1c.hfile2c.h使用しexternal.hます。

  • prog7prog5.cfile6c.cfile4c.c、 、file1c.hfile2c.h使用しexternal.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か?」になります。ヘッダーからそれを省略し、定義呼び出しを and でラップする #define#undef:

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

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

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

externdef.h

/*
** This header must not contain header guards (like <assert.h> must not).
** Each time it is included, the macro HEADER_DEFINING_VARIABLES should
** be defined with the name (in quotes - or possibly angle brackets) of
** the header to be included that defines variables when the macro
** DEFINE_VARIABLES is defined.  See also: external.h (which uses
** DEFINE_VARIABLES and defines macros EXTERN and INITIALIZE
** appropriately).
**
** #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_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;
}

  • prog8prog8.cfile7c.c、を使用しfile9c.cます。

  • prog9prog8.cfile8c.c、を使用しfile9c.cます。


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

グローバル変数を避ける


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

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

注:これらは、かろうじて興味深いものにするのにかろうじて十分なコードしかないおもちゃのプログラムです。例の中には削除できる繰り返しがありますが、教育的な説明を単純化するためのものではありません。prog5.c(例:との違いprog8.cは、含まれているヘッダーの 1 つの名前です。main()関数が繰り返されないようにコードを再編成することは可能ですが、それが明らかにする以上のものを隠します。)

于 2009-09-16T14:37:14.717 に答える
134

変数とは、別の翻訳単位で定義されているextern変数の宣言です (修正してくれた sbi に感謝します)。つまり、変数のストレージが別のファイルに割り当てられます。

と の 2 つのファイルが.cあるtest1.cとしtest2.cます。でグローバル変数を定義し、int test1_var;test1.cこの変数にアクセスしたい場合は、 intest2.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
于 2009-09-16T14:12:24.703 に答える
42

Extern is the keyword you use to declare that the variable itself resides in another translation unit.

So you can decide to use a variable in a translation unit and then access it from another one, then in the second one you declare it as extern and the symbol will be resolved by the linker.

If you don't declare it as extern you'll get 2 variables named the same but not related at all, and an error of multiple definitions of the variable.

于 2009-09-16T14:11:25.987 に答える
29

私は、extern 変数を、コンパイラーに対する約束と考えるのが好きです。

extern に遭遇すると、コンパイラはその型を見つけることしかできず、「存在する」場所を見つけることはできないため、参照を解決できません。

「信じてください。リンク時にこの参照は解決可能になります」と言っているのです。

于 2009-09-16T14:50:53.223 に答える
26
                 declare | define   | initialize |
                ----------------------------------

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

宣言はメモリを割り当てません (変数はメモリ割り当てのために定義する必要があります) が、定義は割り当てます。他の回答は非常に優れているため、これは extern キーワードの別の簡単なビューです。

于 2019-01-09T20:50:02.393 に答える
18

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

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

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

于 2009-09-16T14:18:57.093 に答える
16

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

于 2009-09-16T14:16:24.853 に答える
12

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

于 2012-06-20T23:43:15.577 に答える
8

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

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

于 2012-08-20T10:19:51.157 に答える
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> です。

于 2012-07-02T09:11:11.497 に答える
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

System V ABI Update ELF 仕様の「Symbol Table」の章では、次のように説明されています。

SHN_UNDEF このセクション テーブル インデックスは、シンボルが未定義であることを意味します。リンカーがこのオブジェクト ファイルを、指定されたシンボルを定義する別のファイルと結合すると、このファイルのシンボルへの参照が実際の定義にリンクされます。

これは基本的に、C 標準がextern変数に与える動作です。

これから最終的なプログラムを作成するのはリンカの仕事ですがextern、ソースコードからオブジェクトファイルへの情報はすでに抽出されています。

GCC 4.8 でテスト済み。

C++17 インライン変数

C++17 では、extern 変数の代わりにインライン変数を使用することをお勧めします。インライン変数は使いやすく (ヘッダーで一度だけ定義できます)、より強力です (constexpr をサポート)。参照: C および C++ で「const static」とはどういう意味ですか?

于 2015-05-29T07:34:58.657 に答える
5

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

プログラムに変数や関数にアクセスさせたくない場合は、 which を使用staticして、この変数または関数をこのモジュールの外では使用できないことをコンパイラに伝えます。

于 2012-10-03T04:58:14.327 に答える
4

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

于 2016-01-27T19:47:31.420 に答える
4

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

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

于 2012-08-09T09:21:11.537 に答える
3

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

は、ファイルまたは任意のヘッダー ファイル インクルードでextern宣言できます。first.cfirst.c

于 2014-09-01T07:35:20.020 に答える