나만의 작은 도서관

[C++] extern 키워드와 전역 변수 본문

C++/문법 및 메소드(STL)

[C++] extern 키워드와 전역 변수

pledge24 2025. 4. 9. 18:16

썸네일. 출처: https://medium.com/@bruhout97/extern-keyword-in-the-c-programming-language-1cf8b5903b7b

 

extern 키워드란?

extern int i;
  • extern 키워드“해당 기호(심볼)는 외부(다른 소스 파일 등)에 정의되어 있으며, 여기서는 선언만 한다”라는 의미를 부여한다. 주로 다른 파일에서 정의된 전역 변수나 함수를 가져다 쓸 때 사용한다.
    • (이후 extern 키워드를 사용하는 기호는 “변수” 기준으로 설명)
  • 지역 변수에는 extern 키워드를 사용할 수 없다. 오로지 전역 변수에만 사용한다.
  • extern 키워드는 전역 변수, 함수, 템플릿, 그리고 클래스 등에 적용할 수 있다.
// 아래 심볼들은 다른 소스 파일(.cpp) 어딘가에 정의되어 있다고 가정
extern int g_Score;
extern void PrintScore();  // 함수의 경우 기본이 extern이기 때문에 명시하지 않아도 된다.
extern void (*g_Callback)();  // 어딘가에 정의된 콜백 함수 포인터
extern int g_Numbers[10];  // 배열도 extern으로 선언 가능
extern MyClass globalInstance;

 

 

extern 키워드는 정의가 아닌 선언의 역할만 한다.

  • extern 키워드를 추가한 변수는 일반 변수와 다르게 정의가 아닌 오로지 선언만 한다. 따라서 중복 정의가 발생하지 않는다.
    • 참고로, 기본 타입의 변수들은 선언과 동시에 정의가 된다. 
// file1.cpp
int globalVar = 10; // 정의

// file2.cpp
extern int globalVar; // 선언
// int globalVar; // 중복 정의 발생
  • 같은 이유로 extern 키워드 사용 시 초기화를 동시에 하면 안 된다. extern 키워드와 초기화를 동시에 할 경우, 해당 문장은 선언이 아니라 정의가 되어 다른 곳에 존재하는 a의 정의와 충돌하여 중복 정의가 되어버린다.
extern int a = 15; // 중복 오류 발생!

 

 

+) 간단하게 알아보는 선언과 정의의 차이

구분 선언 정의
의미 "이런 게 있다고 알려주기" "진짜 메모리 할당해서 만들기"
예시 extern int x; int x; 또는 int x = 3;
중복 허용 O (여러 번 가능) X (하나만 가능)

 

 

extern 작동 과정 예제

// file1.cpp
int globalVar = 10; // 전역 변수 정의

// file2.cpp
extern int globalVar; // 전역 변수 선언(정의 아님)

void printVar() {
	cout << globalVar << '\n'; // 사용 가능
}
  • 기본적으로 모든 소스 파일(.cpp)들은 다른 소스 파일이 어떻게 생겼는지 모르기 때문에 file2.cpp에서는 외부에 globalVar이라는 변수의 존재를 모른다.
  • 하지만 extern 키워드를 사용하면 컴파일러에게 “링킹 과정에서 변수 globalVar의 정의를 찾아 연결”하라는 명령이 전달되어, 컴파일러는 globalVar를 file2.cpp에 정의하는 것이 아닌, 다른 소스 파일(file1.cpp)에서 globalVar의 이름으로 정의된 변수에 찾아서 연결한다.
  • 결국, file2.cpp의 globalVar 전역 변수는 컴파일러에 의해 file1.cpp에 정의된 globalVar 전역 변수에 연결되어 같은 메모리를 가리키게 된다. 

일반 전역 변수 vs extern 전역 변수 vs static 전역 변수

 

일반 전역 변수

// file1.cpp
int x = 0; // 전역 변수 x

// file2.cpp
int x = 0; // 전역 변수 x

=> 중복 정의 발생!
  • "일반 전역 변수"는 선언과 동시에 정의가 된다. 또한 서로 다른 소스 파일이라도 같은 메모리 영역에 배치되기 때문에 동일한 이름의 변수를 여러 소스 파일에 정의할 수 없다.
  • 따라서, 위 코드는 중복 정의로 인해 오류가 발생한다.
// file1.h
int x = 0; // 전역 변수 x

// file2.h
int x = 0; // 전역 변수 x

=> 두 헤더 파일을 총 2번 이상 #include로 복사할 경우 중복 정의 발생!
  • 헤더 파일에 정의하는 것은 더더욱 위험하다. 전역 변수가 소스 파일에 정의된 것이 아니기 때문에 #include로 헤더 파일에 복사해가지 않으면 오류는 발생하지 않겠지만(컴파일러는 사용하지 않는 파일은 컴파일 대상에서 제외된다), #include로 2번 이상 복사해갈 경우 중복 정의로 인해 오류가 발생한다.
    • 각 헤더 파일을 한 번씩 사용해도 중복 정의는 똑같이 발생한다.
  • 요약하자면 어떠한 경우에서든 일반 전역 변수를 같은 이름으로 여러 번 정의하여 사용할 수 없다.

extern 전역 변수(외부 전역 변수)

// file1.cpp
int x = 0; // 전역 변수 x

// file2.cpp
extern int x; // 외부 전역 변수 x

=> 중복 정의 X
  • "extern 전역 변수"는 일반 전역 변수와 달리 정의가 아닌 선언만 하므로 중복 정의가 발생하지 않는다.
  • 따라서 위 코드처럼 extern 전역 변수를 사용하면 x에 대한 정의는 file1.cpp의 int x = 0; 으로 유일해지기 때문에 중복 정의가 발생하지 않는다.
// file1.h
extern int x; // 외부 전역 변수 x

// file2.h
extern int x; // 외부 전역 변수 x

=> 중복 정의 X
  • 헤더 파일에서도 extern 키워드를 붙여 변수를 선언하여 사용할 수 있다.
  • 오히려 소스 파일보다 헤더 파일에 더 자주 사용하는데, 헤더 파일을 활용하여 여러 소스 파일에서 전역 변수를 공유할 수 있기 때문이다.
  • 헤더 파일로 전역 변수를 공유하는 정석적인 방법으로 헤더 파일(.h)에 extern 전역 변수를 선언하고, 같은 이름의 소스 파일(.cpp)에 전역 변수들을 정의한 다음, 다른 소스 파일에서 해당 헤더 파일을 #include로 복사해 가는 방법이 있다.

 

정석적인 extern 전역 변수 사용법 예시

// CoreGlobal.h(각 변수들을 선언한다)
extern class ThreadManager* GThreadManager;
extern class GlobalQueue* GGlobalQueue;
extern class JobTimer* GJobTimer;

// CoreGlobal.cpp (각 변수들을 정의한다)
#include "CoreGlobal.h"
ThreadManager* GThreadManager = nullptr;
GlobalQueue* GGlobalQueue = nullptr;
JobTimer* GJobTimer = nullptr;

/*각 변수 초기화 과정 진행*/
...

// otherFile.cpp(헤더를 가져와 전역 변수를 사용)
#include "CoreGlobal.h"

GThreadManager->func1();
GGlobalQueue->func2();
GJobTimer->func3();

static 전역 변수(정적 전역 변수)

// file1.cpp
static int x = 0; // 정적 전역 변수 x

// file2.cpp
static int x = 0; // 정적 전역 변수 x

=> 중복 정의 X

// myHeader1.h
static int x = 0; // 정적 전역 변수 x

// myHeader2.h
static int x = 0; // 정적 전역 변수 x

=> 중복 정의 X
  • "static 전역 변수"는 일반 전역 변수와 다르게 외부에 노출되지 않고, 캡슐화된 전역 변수가 된다. (서로 다른 클래스에 같은 형태의 멤버 변수가 있는 것처럼)
  • static 전역 변수는 정의된 소스 파일에 종속되어 독립적인 메모리 공간을 가진다.
    • 예를 들어, static 전역 변수 x를 정의한 “a.h” 헤더파일을 main.cpp와 other.cpp에서 #include로 복사해 갔다면, main.cpp와 other.cpp에 복사된 각각의 static 전역 변수 x는 서로 다른 변수로 취급된다.
  • 따라서 static 전역 변수는 일반 전역 변수와 동일하게 선언과 동시에 정의가 되지만, 헤더 파일(.h), 소스 파일(.cpp) 상관없이 중복 정의가 발생하지 않는다.
    • 단, 같은 소스 파일에 여러 헤더 파일을 #include 하여 복사해 간 경우 중복 정의가 발생할 수 있다. (아래 코드 참고)
// main.cpp
#include "myHeader1.h" // static int x = 0; // 정적 전역 변수 x
#include "myHeader2.h" // static int x = 0; // 정적 전역 변수 x

int main (void) {}

=> 중복 정의 발생!

 

 

static 전역 변수에 접근하기

  • static 전역 변수는 외부 소스 파일에서 사용할 수 없도록 제한되기 때문에 현재 파일 내부에서만 유효하며, 다른 파일에서 extern 키워드로 외부 참조를 시도하면 링커 오류가 발생한다.
  • 따라서 특정 소스 파일에 정의된 static 전역 변수를 사용하려면 해당 소스 파일에 정의한 함수를 통해 접근해야 한다. 

static 전역 변수 접근 예제

// a.h
#pragma once
static int a = 5;

// other.h
#pragma once
void func1();

// other.cpp
#include <iostream>
#include "other.h"
#include "a.h"

static int x = 0; // other.cpp의 정적 전역 변수 x

void func1()
{
    std::cout << "other.cpp's a value Before " << a << '\\n';
    a = 99;
    std::cout << "other.cpp's a value After " << a << '\\n';
}

// main.cpp
#include <iostream>
#include "other.h"
#include "a.h"

using namespace std;

// extern int x; // 외부 전역 변수 x => 링커 오류 발생!

int main(void)
{
    a = 10;
    cout << "main.cpp's a value Before " << a << '\\n';
    func1(); // other.cpp에 정의된 func1() 함수를 통해 other.cpp의 정적 변수 a에 접근
    cout << "main.cpp's a value After " << a << '\\n';

    return 0;
}

// 실행 결과
// main.cpp's a value Before 10
// other.cpp's a value Before 5
// other.cpp's a value After 99
// main.cpp's a value After 10

extern 전역 변수 vs static 전역 변수

  • 전역 변수를 정의한 헤더 파일을 #include로 여러 소스 파일(.cpp)에서 복사해 가는 경우 extern 전역 변수는 어딘가에 정의된 하나의 변수를 공유하며 “전부 하나의 같은 메모리”를 가리킨다.
  • 반면, static 전역 변수는 정의된 소스 파일에 종속된 공유되지 않는 독립적인 변수가 되며 "서로 다른 메모리"를 가리킨다.
  • 따라서 extern 전역 변수는 이미 정의된 전역 변수를 공유할 때, static 전역 변수는 특정 파일에서만 사용하는 전용 전역 변수를 정의할 때 사용한다. 표로 정리하자면 다음과 같다.

extern 전역 변수와 static 전역 변수 비교표

구분 extern static
목적 외부 선언 – 다른 파일에서 참조 내부 제한 – 선언된 파일에서만 사용 가능
가시성 파일 외부에서도 접근 가능 같은 소스 파일 내부에서만 접근 가능
변수 사용처 전역 변수 공유 전역 변수 은닉
사용 예시 여러 파일에서 공유하는 전역 변수/함수 유틸성 함수나 전역 변수 숨기고 싶을 때

참고 자료

https://kukuta.tistory.com/28

https://learn.microsoft.com/ko-kr/cpp/cpp/extern-cpp?view=msvc-170