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

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

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



답변

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()함수가 반복되지 않도록 코드를 재구성 할 수 있지만 공개 된 것보다 더 많이 숨기는 것입니다.


답변

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


답변

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

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

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


답변

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

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

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


답변

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

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

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


답변

를 추가하면 extern변수 정의 가 변수 선언 으로 바뀝니다 . 선언과 정의의 차이점에 대해서는 이 스레드 를 참조하십시오 .


답변

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

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

선언은 메모리를 할당하지 않지만 (메모리 할당을 위해 변수를 정의해야 함) 정의는 적용됩니다. 다른 답변이 실제로 훌륭하기 때문에 이것은 extern 키워드에 대한 또 다른 간단한 견해입니다.