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

1028
Noname 2009-09-17 04:08.

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

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

17 answers

1813
Jonathan Leffler 2009-09-17 04:37.

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

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

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

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

変数を複数回宣言できます(ただし、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.mkは、prog1専用のメイクファイルです。makeミレニアムの変わり目頃から生産されたほとんどのバージョンで動作します。特にGNUMakeとは関係ありません。

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 —信頼できる唯一の情報源)。

  • ソースファイル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標準の文字および「単一定義規則」に準拠していません—公式には未定義の動作です。

J.2未定義の振る舞い

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

§6.9外部定義¶5

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

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

ただし、C標準では、共通の拡張機能の1つとして有益な付録Jにも記載されています。

J.5.11複数の外部定義

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

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

上記のファイルの1つが、ではなくlとして宣言されdoubleているlong場合、Cのタイプセーフでないリンカーはおそらく不一致を検出しません。64ビットlongとを搭載したマシンを使用している場合はdouble、警告も表示されません。32ビットlongと64ビットのマシンでdoubleは、サイズの違いについて警告が表示される可能性があります。Fortranプログラムが一般的なブロックの最大サイズを使用するのとまったく同じように、リンカーは最大サイズを使用します。

2020-05-07にリリースされたGCC10.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

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

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

broken_header.h

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

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

seldom_correct.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;
}
  • 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ブロックの内容を逆にし、DenisKniazhevによって特定されたバグを修正します

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番目の呼び出しに対する最初の引数はINITIALIZERisで{ 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

ヘッダーガード

型定義(enum、struct、union型、または一般的に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. 変数を定義または宣言するヘッダーには、定型化された内容が含まれています。

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


次のソースファイルのソースを(メインプログラムを提供する)が完了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

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

  • prog8用途prog8.cfile7c.cfile9c.c

  • prog9用途prog8.cfile8c.cfile9c.c


ただし、特に次のような標準的なアドバイスを受けた場合、実際に問題が発生する可能性は比較的低くなります。

グローバル変数を避ける


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

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

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

125
Johannes Weiss 2009-09-17 04:12.

extern変数は、別の翻訳単位で定義された変数の宣言(おかげで補正するためのSBIする)です。つまり、変数のストレージは別のファイルに割り当てられます。

2つの.c-filestest1.ctest2.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
41
Arkaitz Jimenez 2009-09-17 04:11.

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

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

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

26
Buggieboy 2009-09-17 04:50.

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

externに遭遇すると、コンパイラはその型のみを検出でき、「存在する」場所は検出できないため、参照を解決できません。

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

18
BenB 2009-09-17 04:18.

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

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

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

17
Lucian Nut 2019-01-10 10:50.
                 declare | define   | initialize |
                ----------------------------------

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

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

16
sbi 2009-09-17 04:16.

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

12
Alex Lockwood 2012-06-21 13:43.

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

9
Anup 2012-08-21 00:19.

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

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

9
Phoenix225 2012-07-02 23:11.

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 ELFLinuxの実装

他の回答は言語使用の側面をカバーしているので、それがこの実装でどのように実装されているかを見てみましょう。

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情報はすでにソースコードからオブジェクトファイルに抽出されています。

GCC4.8でテスト済み。

C ++ 17インライン変数

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

6
loganaayahee 2012-10-03 18:58.

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

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

5
user1270846 2012-08-09 23:21.

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

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

5
Geremia 2016-01-28 09:47.

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

4
shoham 2014-09-01 21:35.

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

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

3
user50619 2018-10-10 00:01.

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として宣言されます

extern unsigned char testing_mode;

ただし、として正しく宣言されますunsigned char

古いフォーラムの投稿は、おそらくこれをもう少し明確に説明しています。しかし、これは、gotchaあるファイルで変数を宣言し、別のファイルで別の型としてexternを宣言できるコンパイラーを使用する場合の本当の可能性です。これに関連する問題は、testing_modeを別のファイルでintとして宣言すると、16ビット変数であると見なされ、RAMの他の部分が上書きされ、別の変数が破損する可能性があることです。デバッグが難しい!

1
muusbolla 2019-06-24 16:23.

ヘッダーファイルにextern参照またはオブジェクトの実際の実装を含めることができるようにするために使用する非常に短いソリューション。実際にオブジェクトを含むファイルは、ただそうし#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

MORE COOL STUFF

「水曜日」シーズン1の中心には大きなミステリーがあります

「水曜日」シーズン1の中心には大きなミステリーがあります

Netflixの「水曜日」は、典型的な10代のドラマ以上のものであり、実際、シーズン1にはその中心に大きなミステリーがあります.

ボディーランゲージの専門家は、州訪問中にカミラ・パーカー・ボウルズが輝くことを可能にした微妙なケイト・ミドルトンの動きを指摘しています

ボディーランゲージの専門家は、州訪問中にカミラ・パーカー・ボウルズが輝くことを可能にした微妙なケイト・ミドルトンの動きを指摘しています

ケイト・ミドルトンは、州の夕食会と州の訪問中にカミラ・パーカー・ボウルズからスポットライトを奪いたくなかった、と専門家は言う.

一部のファンがハリー・スタイルズとオリビア・ワイルドの「非常に友好的な」休憩が永続的であることを望んでいる理由

一部のファンがハリー・スタイルズとオリビア・ワイルドの「非常に友好的な」休憩が永続的であることを望んでいる理由

一部のファンが、オリビア・ワイルドが彼女とハリー・スタイルズとの間の「難しい」が「非常に友好的」な分割を恒久的にすることを望んでいる理由を見つけてください.

エリザベス女王の死後、ケイト・ミドルトンはまだ「非常に困難な時期」を過ごしている、と王室の専門家が明らかにする 

エリザベス女王の死後、ケイト・ミドルトンはまだ「非常に困難な時期」を過ごしている、と王室の専門家が明らかにする&nbsp;

エリザベス女王の死後、ケイト・ミドルトンが舞台裏で「非常に困難な時期」を過ごしていたと伝えられている理由を調べてください.

セントヘレナのジェイコブのはしごを登るのは、気弱な人向けではありません

セントヘレナのジェイコブのはしごを登るのは、気弱な人向けではありません

セント ヘレナ島のジェイコブズ ラダーは 699 段の真っ直ぐ上る階段で、頂上に到達すると証明書が発行されるほどの難易度です。

The Secrets of Airline Travel Quiz

The Secrets of Airline Travel Quiz

Air travel is far more than getting from point A to point B safely. How much do you know about the million little details that go into flying on airplanes?

Where in the World Are You? Take our GeoGuesser Quiz

Where in the World Are You? Take our GeoGuesser Quiz

The world is a huge place, yet some GeoGuessr players know locations in mere seconds. Are you one of GeoGuessr's gifted elite? Take our quiz to find out!

バイオニック読書はあなたをより速く読むことができますか?

バイオニック読書はあなたをより速く読むことができますか?

BionicReadingアプリの人気が爆発的に高まっています。しかし、それは本当にあなたを速読術にすることができますか?

パンデミックは終わったかもしれないが、Covid-19 は終わっていない

パンデミックは終わったかもしれないが、Covid-19 は終わっていない

2021 年 6 月 8 日にニューヨーク市で開催された covid-19 パンデミックで亡くなった人々の命を偲び、祝うために、ネーミング ザ ロスト メモリアルズが主催するイベントと行進の最中に、グリーンウッド墓地の正門から記念碑がぶら下がっています。週末、ジョー・バイデン大統領は、covid-19 パンデミックの終息を宣言しました。これは、過去 2 年以上にわたり、公の場でそうするための長い列の中で最新のものです。

デビル・イン・オハイオの予告編は、エミリー・デシャネルもオハイオにいることを明らかにしています

デビル・イン・オハイオの予告編は、エミリー・デシャネルもオハイオにいることを明らかにしています

オハイオ州のエミリー・デシャネル みんな早く来て、ボーンズが帰ってきた!まあ、ショーボーンズではなく、彼女を演じた俳優. エミリー・デシャネルに最後に会ってからしばらく経ちました.Emily Deschanel は、長期にわたるプロシージャルな Bones の Temperance “Bones” Brennan としてよく知られています。

ドナルド・トランプはFBIのマー・ア・ラーゴ襲撃映像をリリースする予定ですか?

ドナルド・トランプはFBIのマー・ア・ラーゴ襲撃映像をリリースする予定ですか?

どうやら、ドナルド・トランプに近い人々は、今月初めにFBIによって家宅捜索された彼のMar-a-Lago財産からの映像を公開するよう彼に勧めています. 前大統領はテープを公開するかどうかを確認していませんが、息子はフォックス・ニュースにそうなるだろうと語った.

Andor は、他の Star Wars ショーから大きな距離を置きます。

Andor は、他の Star Wars ショーから大きな距離を置きます。

アンドールの一場面。数十年前、ジョージ・ルーカスがスター・ウォーズのテレビ番組を制作するのを妨げた主な理由は、お金でした。

ケイト・ミドルトンとウィリアム王子は、彼らが子供たちと行っているスパイをテーマにした活動を共有しています

ケイト・ミドルトンとウィリアム王子は、彼らが子供たちと行っているスパイをテーマにした活動を共有しています

ケイト・ミドルトンとウィリアム王子は、子供向けのパズルの本の序文を書き、ジョージ王子、シャーロット王女、ルイ王子と一緒にテキストを読むと述べた.

事故で押しつぶされたスイカは、動物を喜ばせ水分補給するために野生生物保護団体に寄付されました

事故で押しつぶされたスイカは、動物を喜ばせ水分補給するために野生生物保護団体に寄付されました

Yak's Produce は、数十個のつぶれたメロンを野生動物のリハビリ専門家であるレスリー グリーンと彼女のルイジアナ州の救助施設で暮らす 42 匹の動物に寄付しました。

デミ・ロヴァートは、新しいミュージシャンのボーイフレンドと「幸せで健康的な関係」にあります: ソース

デミ・ロヴァートは、新しいミュージシャンのボーイフレンドと「幸せで健康的な関係」にあります: ソース

8 枚目のスタジオ アルバムのリリースに向けて準備を進めているデミ ロヴァートは、「スーパー グレート ガイ」と付き合っている、と情報筋は PEOPLE に確認しています。

Plathville の Kim と Olivia Plath が数年ぶりに言葉を交わすことへようこそ

Plathville の Kim と Olivia Plath が数年ぶりに言葉を交わすことへようこそ

イーサン プラスの誕生日のお祝いは、TLC のウェルカム トゥ プラスビルのシーズン 4 のフィナーレで、戦争中の母親のキム プラスと妻のオリビア プラスを結びつけました。

仕事の生産性を高める 8 つのシンプルなホーム オフィスのセットアップのアイデア

仕事の生産性を高める 8 つのシンプルなホーム オフィスのセットアップのアイデア

ホームオフィスのセットアップ術を極めよう!AppExert の開発者は、家族全員が一緒にいる場合でも、在宅勤務の技術を習得しています。祖父や曽祖父が共同家族で暮らしていた頃の記憶がよみがえりました。

2022 年、私たちのデジタル ライフはどこで終わり、「リアル ライフ」はどこから始まるのでしょうか?

20 年前のタイムトラベラーでさえ、日常生活におけるデジタルおよびインターネットベースのサービスの重要性に驚くことでしょう。MySpace、eBay、Napster などのプラットフォームは、高速化に焦点を合わせた世界がどのようなものになるかを示してくれました。

ニューロマーケティングの秘密科学

ニューロマーケティングの秘密科学

マーケティング担当者が人間の欲望を操作するために使用する、最先端の (気味が悪いと言う人もいます) メソッドを探ります。カートをいっぱいにして 3 桁の領収書を持って店を出る前に、ほんの数点の商品を買いに行ったことはありませんか? あなたは一人じゃない。

地理情報システムの日: GIS 開発者として学ぶべき最高の技術スタック

地理情報システムの日: GIS 開発者として学ぶべき最高の技術スタック

私たちが住んでいる世界を確実に理解するには、データが必要です。ただし、空間参照がない場合、このデータは地理的コンテキストがないと役に立たなくなる可能性があります。

Language