extern을 사용하여 소스 파일간에 변수를 공유하는 방법


987

C의 전역 변수에 때로는 extern키워드 가 있다는 것을 알고 있습니다. extern변수 란 무엇입니까 ? 선언은 무엇입니까? 범위는 무엇입니까?

소스 파일에서 변수를 공유하는 것과 관련이 있지만 어떻게 정확하게 작동합니까? 어디서 사용 extern합니까?

답변:


1751

extern빌드하는 프로그램이 서로 연결된 여러 소스 파일로 구성된 경우에만 사용하는 것이 중요합니다. 예를 들어 소스 파일에 정의 된 일부 변수는와 file1.c같은 다른 소스 파일에서 참조되어야합니다 file2.c.

변수 정의 와 변수 선언 의 차이점이해하는 것이 중요 합니다 .

  • 변수는 변수가 존재 함을 컴파일러에 알릴 때 선언 됩니다 (그리고 이것이 그 유형입니다). 해당 시점에서 변수에 대한 스토리지를 할당하지 않습니다.

  • 변수는 컴파일러가 변수에 대한 스토리지를 할당 할 때 정의 됩니다.

변수를 여러 번 선언 할 수 있습니다 (한 번이면 충분). 주어진 범위 내에서 한 번만 정의 할 수 있습니다. 변수 정의도 선언이지만 모든 변수 선언이 정의는 아닙니다.

전역 변수를 선언하고 정의하는 가장 좋은 방법

전역 변수를 선언하고 정의하는 깨끗하고 안정적인 방법은 헤더 파일을 사용 하여 변수 extern 선언 을 포함하는 것 입니다.

헤더는 변수를 정의하는 하나의 소스 파일과 변수를 참조하는 모든 소스 파일에 의해 포함됩니다. 각 프로그램에 대해 하나의 소스 파일 (하나의 소스 파일 만)이 변수를 정의합니다. 마찬가지로 하나의 헤더 파일 (및 하나의 헤더 파일) 만 변수를 선언해야합니다. 헤더 파일은 매우 중요합니다. 독립적 인 TU (번역 단위-소스 파일 생각) 간의 교차 점검을 가능하게하고 일관성을 보장합니다.

다른 방법이 있지만이 방법은 간단하고 신뢰할 수 있습니다. 그것은에 의해 입증되어 file3.h, file1.cfile2.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++);
}

이것이 전역 변수를 선언하고 정의하는 가장 좋은 방법입니다.


다음 두 파일은 다음에 대한 소스를 완성합니다 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.c, file1.c, file2.c, file3.hprog1.h.

파일 prog1.mk은 makefile prog1전용입니다. 그것은 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됩니다. 절대 또는 정규화되지 않은 변수 정의입니다.

  • 주어진 변수에 대해 단 하나의 헤더 파일 만 선언합니다 (SPOT-Single Point of Truth).

  • 소스 파일에는 extern변수 선언이 포함되지 않습니다. 소스 파일에는 항상 변수를 선언하는 (sole) 헤더가 포함됩니다.

  • 주어진 변수에 대해 정확히 하나의 소스 파일이 변수를 정의하고 변수를 초기화하는 것이 좋습니다. (0으로 명시 적으로 초기화 할 필요는 없지만 프로그램에는 특정 전역 변수에 대해 초기화 된 정의가 하나만있을 수 있기 때문에 해를 끼치 지 않고 좋은 결과를 얻을 수 있습니다).

  • 변수를 정의하는 소스 파일에는 정의와 선언의 일관성을 유지하기 위해 헤더도 포함됩니다.

  • 함수는를 사용하여 변수를 선언 할 필요가 없습니다 extern.

  • 가능하면 전역 변수를 피하십시오. 대신 함수를 사용하십시오.

이 답변의 소스 코드와 텍스트 는 src / so-0143-3204 하위 디렉토리의 GitHub의 SOQ (Stack Overflow Questions) 저장소에서 사용할 수 있습니다 .

숙련 된 C 프로그래머가 아니라면 여기서 읽기를 중단 할 수 있습니다.

전역 변수를 정의하는 좋은 방법은 아닙니다.

일부 (실제로 많은) C 컴파일러를 사용하면 변수의 '공통'정의를 피할 수 있습니다. 여기서 '공통'은 포트란에서 (명명 적으로 명명 된) COMMON 블록을 사용하여 소스 파일간에 변수를 공유하는 데 사용되는 기술을 나타냅니다. 여기서 발생하는 것은 여러 파일 각각이 변수의 임시 정의를 제공한다는 것입니다. 하나 이상의 파일이 초기화 된 정의를 제공하지 않는 한 다양한 파일은 변수의 공통 단일 정의를 공유하게됩니다.

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 정의되지 않은 동작

외부 연계가있는 식별자가 사용되지만 프로그램에는 식별자에 대한 외부 정의가 정확히 하나 존재하지 않거나 식별자가 사용되지 않으며 식별자에 대한 외부 정의가 여러 개 있습니다 (6.9).

§6.9 외부 정의 ¶5

외부 정의는 또한 함수 (인라인 정의 이외) 또는 객체의 정의 된 외부 선언한다. 외부 링크 선언 식별자 (a의 오퍼랜드의 일부 이외의 식을 사용하는 경우 sizeof또는 _Alignof결과 정수 상수 연산자), 대체로 전체 프로그램의 식별을위한 정확히 하나의 외부 정의가한다; 그렇지 않으면 하나만있을 것입니다. 161)

161) 따라서 외부 연결로 선언 된 식별자가 표현식에 사용되지 않으면 외부 정의가 필요하지 않습니다.

그러나, C 표준은 또한 중 하나로 정보 부속서 J에 나타 일반적인 확장 .

J.5.11 다중 외부 정의

extern 키워드를 명시 적으로 사용하거나 사용하지 않고 오브젝트의 식별자에 대한 둘 이상의 외부 정의가있을 수 있습니다. 정의가 일치하지 않거나 둘 이상이 초기화되면 동작이 정의되지 않습니다 (6.9.2).

이 기술이 항상 지원되는 것은 아니므로 사용하지 않는 것이 가장 좋습니다. 특히 코드를 이식 할 수 있어야하는 경우 . 이 기술을 사용하면 의도하지 않은 유형의 구멍을 낼 수도 있습니다.

위의 파일 중 하나가 ldouble대신 선언 된 long경우 C의 형식이 안전하지 않은 링커는 아마도 일치하지 않습니다. 64 비트 long및 을 사용하는 컴퓨터를 사용 double하는 경우 경고가 표시되지 않습니다. 32 비트 컴퓨터에서long 및 64double 에서는 다른 크기에 대한 경고가 표시 될 수 있습니다. Fortran 프로그램에서 가장 일반적인 크기의 블록을 사용하는 것과 마찬가지로 링커에서 가장 큰 크기를 사용합니다.

2020-05-07에 릴리스 된 GCC 10.1.0은 기본 컴파일 옵션을로 변경합니다. 즉, 기본값을 사용 -fno-common하여 기본값을 무시 -fcommon하거나 속성 등을 사용 하지 않으면 위의 코드가 더 이상 연결되지 않습니다. 링크 참조).


다음 두 파일은 다음에 대한 소스를 완성합니다 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.c, file10.c, file11.c, file12.c, prog2.h.

경고

여기에 의견과 비슷한 질문에 대한 내 대답에 명시된 바와 같이 전역 변수에 여러 정의를 사용하면 정의되지 않은 동작 (J.2; §6.9)이 발생합니다. 발생할 수있는 것 중 하나는 프로그램이 예상대로 작동한다는 것입니다. 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 : 헤더가 변수를 정의하고 초기화하면 지정된 프로그램에서 하나의 소스 파일 만 헤더를 사용할 수 있습니다. 헤더는 주로 정보를 공유하기위한 것이므로 한 번만 사용할 수있는 헤더를 만드는 것은 약간 어리 석습니다.

seldom_correct.h

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

참고 3 : 헤더가 정적 변수 (초기화 유무에 관계없이)를 정의하면 각 소스 파일은 고유 한 '전역'변수의 개인 버전으로 끝납니다.

예를 들어 변수가 실제로 복잡한 배열 인 경우 코드가 크게 복제 될 수 있습니다. 매우 가끔 효과를 얻을 수있는 현명한 방법 일 수 있지만 매우 드문 일입니다.


요약

내가 처음 보여준 헤더 기술을 사용하십시오. 그것은 어디서나 안정적으로 작동합니다. 특히를 선언하는 헤더는 헤더를 정의하는 헤더 global_variable를 포함 하여 헤더 를 사용하는 모든 파일에 포함됩니다. 이렇게하면 모든 것이 일관되게 유지됩니다.

함수를 선언하고 정의 할 때도 비슷한 문제가 발생합니다. 유사한 규칙이 적용됩니다. 그러나 질문은 변수에 관한 것이기 때문에 변수에 대해서만 답을 유지했습니다.

원래 답변의 끝

숙련 된 C 프로그래머가 아니라면 여기서 읽기를 중단해야합니다.


늦은 주요 추가

코드 중복 방지

여기에 설명 된 '헤더 선언, 소스 정의'메커니즘에 대해 때때로 (그리고 합법적으로) 제기되는 한 가지 우려는 동기화 될 두 파일 (헤더와 소스)이 있다는 것입니다. 일반적으로 매크로를 사용하여 헤더가 이중 의무 (일반적으로 변수를 선언 함)를 제공 할 수 있다는 관찰이 이어지지 만 헤더가 포함되기 전에 특정 매크로가 설정되면 대신 변수를 정의합니다.

또 다른 관심사는 변수가 여러 '주요 프로그램'각각에 정의되어야한다는 것입니다. 이것은 일반적으로 가짜 관심사입니다. 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++);
}

다음 두 파일은 다음에 대한 소스를 완성합니다 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.c, file1a.c, file2a.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 });

뒷면의 내용 #if#else블록, 버그에 의해 식별 한 고정 데니스 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 { 41이고 나머지 인수 (이 예에서는 단수)는 43 }입니다. 매크로에 대한 가변 인수 목록에 대한 C99 또는 유사한 지원이 없으면 쉼표를 포함해야하는 이니셜 라이저는 매우 문제가됩니다.

Denis Kniazhev에 따라 올바른 헤더 file3b.h포함 (대신 fileba.h)


다음 두 파일은 다음에 대한 소스를 완성합니다 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.c, file1b.c, file2b.c, prog4.h, file3b.h.

헤더 가드

형식 정의 (enum, struct 또는 union 형식 또는 typedef)가 문제를 일으키지 않도록 모든 헤더를 재 연합으로부터 보호해야합니다. 표준 기술은 다음과 같은 헤더 가드로 헤더 본문을 감싸는 것입니다.

#ifndef FILE3B_H_INCLUDED
#define FILE3B_H_INCLUDED

...contents of header...

#endif /* FILE3B_H_INCLUDED */

헤더는 간접적으로 두 번 포함될 수 있습니다. 예를 들어, 표시되지 않은 유형 정의에 대해 file4b.h포함 file3b.h하고 file1b.c헤더 file4b.h와를 모두 사용해야 file3b.h하는 경우 해결해야 할 몇 가지 까다로운 문제가 있습니다. 분명히 헤더 목록을 수정하여 just을 포함 할 수 있습니다 file4b.h. 그러나 내부 종속성을 인식하지 못할 수 있으며 코드는 이상적으로 계속 작동해야합니다.

또한 정의를 생성 file4b.h하기 file3b.h위해 포함 하기 전에 포함 시킬 수 있기 때문에 까다로워지기 시작 하지만 일반 헤더 가드를 file3b.h사용하면 헤더가 다시 포함되지 않습니다.

따라서 file3b.h선언의 경우 최대 한 번, 정의의 경우 최대 한 번의 본문을 포함해야하지만 단일 변환 단위 (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여러 번,하지만 그 메커니즘이 작동 보여 가장 간단한 방법입니다. 이는 헤더가 간접적으로 두 번 포함 된 경우에도 안전하다는 것을 의미합니다.

이것에 대한 제한은 다음과 같습니다.

  1. 전역 변수를 정의하거나 선언하는 헤더 자체는 어떤 유형도 정의 할 수 없습니다.

  2. 변수를 정의해야하는 헤더를 포함하기 직전에 매크로 DEFINE_VARIABLES를 정의합니다.

  3. 변수를 정의하거나 선언하는 헤더에는 양식화 된 내용이 있습니다.

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

다음 소스 파일의 소스를 (주 프로그램을 제공) 완료 prog5, prog6prog7:

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.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.hfile2d.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#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_VARIABLES에서 file2d.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; }

다음 두 파일은 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.c, file7c.c, file9c.c.

  • prog9용도 prog8.c, file8c.c, file9c.c.


그러나 특히 표준 조언을받는 경우 문제가 실제로 발생할 가능성은 거의 없습니다.

전역 변수를 피하십시오


이 박람회는 아무것도 그리워합니까?

고백 : 여기에 설명 된 '중복 코드 피하기'체계는 문제가 내가 작업하고 있지만 소유하지 않는 일부 코드에 영향을 미치기 때문에 개발되었으며, 답변의 첫 번째 부분에 요약 된 체계와 관련이 있습니다. 그러나 원래 체계는 변수 정의와 선언을 동기화하기 위해 수정해야 할 위치가 두 개뿐입니다. 이는 코드 기반 전체에 외부 변수 선언이 흩어져있는 것보다 훨씬 큰 발전입니다 (총 수천 개의 파일이있을 때 중요 함) . 그러나, 이름을 가진 파일의 코드 fileNc.[ch](플러스 external.hexterndef.h는 작업을 할 수 있음)을 보여줍니다. 헤더 파일을 정의하고 선언하는 변수에 대한 표준화 된 템플릿을 제공하는 헤더 생성기 스크립트를 작성하는 것은 어렵지 않습니다.

NB 이것들은 약간 흥미로워 질 정도로 코드가 거의없는 장난감 프로그램입니다. 예제 내에서 제거 될 수있는 반복이 있지만 교육 학적 설명을 단순화하지는 않습니다. 예를 들어, 포함 된 헤더 중 하나의 이름 prog5.c과 의 차이점 prog8.cmain()함수가 반복되지 않도록 코드를 재구성 할 수 있지만 공개 된 것보다 더 많이 숨기는 것입니다.


3
@litb : 공통 정의는 부록 J.5.11을 참조하십시오. 이는 공통된 확장입니다.
Jonathan Leffler

3
@litb : 나는 피해야한다는 데 동의합니다. 그래서 그것이 '글로벌 변수를 정의하는 좋은 방법은 아닙니다'섹션에 있습니다.
Jonathan Leffler

3
실제로 이것은 일반적인 확장이지만 프로그램이 의존하는 정의되지 않은 동작입니다. 나는 이것이 C 자신의 규칙에 의해 허용된다고 말하고 있는지 확실하지 않았습니다. 이제 나는 그것이 일반적인 확장이라고 말하고 코드를 이식 가능 해야하는 경우 피하는 것을 피합니다. 그래서 의심의 여지없이 당신을 공감할 수 있습니다. 정말 좋은 답변 IMHO :)
Johannes Schaub-litb

19
상단에 멈춰 있으면 간단한 일이 간단 해집니다. 더 자세히 읽으면 더 많은 뉘앙스, 합병증 및 세부 사항을 처리합니다. 방금 경험이 적은 C 프로그래머 또는 이미 해당 주제를 알고있는 C 프로그래머를 위해 두 개의 '초기 정지 점'을 추가했습니다. 이미 답을 알고 있다면 모든 것을 읽을 필요는 없습니다 (그러나 기술적 결함이 발견되면 알려주십시오).
Jonathan Leffler

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.
Jonathan Leffler

125

extern변수는 다른 변환 부에 정의 된 변수 (보정하기 위해 (sbi)를 감사)을 선언한다. 이는 변수의 스토리지가 다른 파일에 할당되었음을 의미합니다.

당신이이 말 .c-files test1.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

21
"의사 정의"는 없습니다. 선언입니다.
sbi

3
위의 예에서, I가 변화하는 경우 extern int test1_var;int test1_var;, 링커 (GCC 5.4.0)은 여전히 통과한다. 그래서되어 extern정말이 경우에 필요?
radiohead

2
@ radiohead : 내 대답에서 , 삭제는 extern자주 작동하는 일반적인 확장이며 특히 GCC와 함께 작동한다는 정보를 찾을 수 있습니다 (그러나 GCC는 그것을 지원하는 유일한 컴파일러가 아닙니다. 유닉스 시스템에서 널리 사용됩니다). 내 대답에서 "J.5.11"또는 "그다지 좋지 않은 방법"섹션을 찾을 수 있고 그 길이 긴 텍스트가 설명되어 있습니다 (또는 그렇게하려고합니다).
Jonathan Leffler 2016 년

extern 선언은 확실히 다른 번역 단위로 정의 할 필요는 없으며 일반적으로 그렇지 않습니다. 실제로 선언과 정의는 하나 일 수 있습니다.
Monica를 기억하십시오

40

Extern은 변수 자체가 다른 번역 단위에 있음을 선언하는 데 사용하는 키워드입니다.

따라서 번역 단위에서 변수를 사용하고 다른 변수에서 액세스하기로 결정한 다음 두 번째 변수에서 extern으로 선언하면 심볼이 링커에 의해 해석됩니다.

extern으로 선언하지 않으면 이름은 같지만 전혀 관련이없는 2 개의 변수와 변수에 대한 여러 정의 오류가 발생합니다.


5
즉, extern이 사용되는 변환 단위는이 변수, 해당 유형 등에 대해 알고 있으므로 기본 논리의 소스 코드에서이를 사용할 수 있지만 변수를 할당 하지 않으면 다른 변환 단위가이를 수행합니다. 두 변환 단위가 변수를 정상적으로 선언하는 경우 변수에 대한 두 개의 실제 위치 (컴파일 된 코드 내에서 연관된 "잘못된"참조 및 링커에 대한 모호함)가 효과적으로 발생합니다.
mjv

26

extern 변수를 컴파일러에게 약속한다고 생각하고 싶습니다.

extern이 발생하면 컴파일러는 "살아있는"곳이 아닌 해당 유형 만 찾을 수 있으므로 참조를 확인할 수 없습니다.

"신뢰하십시오. 링크 타임에이 참조를 해결할 수 있습니다."


보다 일반적으로, 선언 은 이름이 링크 타임에 정확히 하나의 정의로 해석 될 수 있다는 약속입니다. extern은 정의하지 않고 변수를 선언합니다.
Lie Ryan

18

extern은 컴파일러에게이 변수에 대한 메모리가 다른 곳에서 선언되었다는 것을 신뢰하도록 지시하므로 메모리 할당 / 확인을 시도하지 않습니다.

따라서 extern을 참조하는 파일을 컴파일 할 수 있지만 해당 메모리가 어딘가에 선언되지 않으면 링크 할 수 없습니다.

전역 변수 및 라이브러리에는 유용하지만 링커가 검사를 입력하지 않으므로 위험합니다.


메모리가 선언되지 않았습니다. 다음이 질문에 대한 답변을 참조하십시오 stackoverflow.com/questions/1410563 자세한 내용을.
sbi

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 키워드에 대한 또 다른 간단한 견해입니다.


11

extern에 대한 올바른 해석은 컴파일러에게 무언가를 알려주는 것입니다. 컴파일러에 현재 존재하지는 않지만 선언 된 변수는 링커 (일반적으로 다른 객체 (파일))에서 어떻게 든 찾을 수 있다고 알려줍니다. 그런 다음 링커는 extern 선언 여부에 관계없이 모든 것을 찾아서 정리할 수있는 운이 좋은 사람이 될 것입니다.


8

C에서 example.c 파일에 변수가 로컬 범위가 있다고 말합니다. 컴파일러는 변수가 동일한 파일 example.c 내에 정의를 가질 것으로 예상하고 동일한 파일을 찾지 못하면 오류를 발생시킵니다. 반면에 함수는 기본적으로 전역 범위를가집니다. 따라서 컴파일러에게 "look look dude ...를 언급 할 필요는 없습니다.이 함수의 정의는 여기에서 찾을 수 있습니다". 선언을 포함하는 파일을 포함하는 함수로는 충분합니다 (실제로 헤더 파일을 호출하는 파일). 예를 들어 다음 두 파일을 고려하십시오.
example.c

#include<stdio.h>
extern int a;
main(){
       printf("The value of a is <%d>\n",a);
}

예 1.c

int a = 5;

이제 다음 명령을 사용하여 두 파일을 함께 컴파일 할 때

단계 1) cc -o 예 예 c 예 1.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 지원). 참조 : 무엇을 'const를 정적'을 의미 C 및 C ++?


3
내 투표가 아니기 때문에 모르겠습니다. 그러나 나는 의견을 제시 할 것이다. 의 결과를 readelf보거나 nm도움이 될 수 있지만을 사용하는 방법의 기본 사항을 설명 extern하지 않았거나 실제 정의로 첫 번째 프로그램을 완료하지 않았습니다. 귀하의 코드는조차 사용하지 않습니다 notExtern. 명명법 문제도 있습니다.로 notExtern선언 된 것이 아니라 여기에 정의되어 있지만 extern변환 단위에 적합한 선언이 포함 된 경우 다른 소스 파일에서 액세스 할 수있는 외부 변수입니다 ( extern int notExtern;! 필요 ).
Jonathan Leffler

1
@JonathanLeffler 의견 주셔서 감사합니다! 표준 행동 및 사용 권장 사항은 이미 다른 답변에서 수행되었으므로 진행 상황을 파악하는 데 실제로 도움이되는 구현을 조금 보여 주기로 결정했습니다. 사용하지 않는 notExtern것은 못 생겼습니다. 명명법에 대해 더 나은 이름이 있으면 알려주십시오. 물론 그것은 실제 프로그램에 대한 좋은 이름은 아니지만 여기서는 교훈적인 역할에 잘 맞는다고 생각합니다.
Ciro Santilli 冠状 病毒 审查 六四 事件 法轮功

이름에 관해서는 global_def여기에 정의 된 변수와 extern_ref다른 모듈에 정의 된 변수는 어떻습니까? 그것들은 적절하게 명확한 대칭을 가질 것입니까? int extern_ref = 57;정의 된 파일에서 여전히 또는 그와 비슷한 결과를 얻으므로 이름이 이상적이지는 않지만 단일 소스 파일의 맥락에서 합리적인 선택입니다. 갖는 extern int global_def;헤더에하는 것은 많은 문제, 그것은 나에게 보인다 없습니다. 물론 전적으로 당신에게 달려 있습니다.
Jonathan Leffler

7

extern 키워드는 전역 변수로 식별하기 위해 변수와 함께 사용됩니다.

또한 다른 파일에서 선언 / 정의되었지만 모든 파일에서 extern 키워드를 사용하여 선언 된 변수를 사용할 수 있음을 나타냅니다.


5

extern 프로그램의 한 모듈이 프로그램의 다른 모듈에 선언 된 전역 변수 또는 함수에 액세스 할 수 있습니다. 일반적으로 헤더 파일에 extern 변수가 선언되어 있습니다.

프로그램이 변수 또는 함수에 액세스하지 않게 static하려면 컴파일러에서이 변수 또는 함수를이 모듈 외부에서 사용할 수 없음을 알려줍니다.


5

extern 단순히 변수가 다른 곳에 정의되어 있음을 의미합니다 (예 : 다른 파일).


4

먼저, extern키워드는 변수를 정의하는 데 사용되지 않습니다. 오히려 변수를 선언하는 데 사용됩니다. extern데이터 유형이 아닌 스토리지 클래스 라고 말할 수 있습니다 .

extern다른 C 파일 또는 외부 구성 요소에이 변수가 이미 정의되어 있음을 알리는 데 사용됩니다. 예 : 라이브러리를 빌드하는 경우 라이브러리 자체의 어딘가에 글로벌 변수를 정의 할 필요가 없습니다. 라이브러리는 직접 컴파일되지만 파일을 링크하는 동안 정의를 확인합니다.


3

externfirst.c파일 이 다른 파일의 전역 매개 변수에 대한 전체 액세스 권한을 가질 수 있도록 사용됩니다second.c 파일 됩니다.

extern에서 선언 할 수 있습니다 first.c파일이나 헤더 파일 중 하나가에 first.c포함되어 있습니다.


3
점을 유의 extern선언하지에, 헤더에 있어야 first.c유형이 변경되면, 그래서 선언도 변경됩니다. 또한 second.c정의가 선언과 일치하도록 변수를 선언하는 헤더를 포함해야합니다 . 헤더의 선언은 그것을 모두 고정시키는 접착제입니다. 파일을 개별적으로 컴파일 할 수 있지만 전역 변수 유형에 대한 일관된보기를 보장합니다.
Jonathan Leffler

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으로 선언됩니다 .

extern unsigned char testing_mode;

그러나로 올바르게 선언됩니다 unsigned char.

이전 포럼 게시물은 아마도 이것을 좀 더 명확하게 설명했을 것입니다. 그러나 gotcha컴파일러를 사용하면 한 파일에서 변수를 선언 한 다음 다른 유형의 extern을 다른 유형으로 선언 할 수 있는 진정한 잠재력 이 있습니다. 이와 관련된 문제는 testing_mode를 다른 파일의 int로 선언하면 16 비트 var라고 생각하고 램의 다른 부분을 덮어 쓰면 잠재적으로 다른 변수가 손상 될 수 있습니다. 디버깅하기 어려움!


0

헤더 파일에 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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.