나만의 작은 도서관
[C++] extern 키워드와 전역 변수 본문
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://learn.microsoft.com/ko-kr/cpp/cpp/extern-cpp?view=msvc-170
'C++ > 문법 및 메소드(STL)' 카테고리의 다른 글
[C++][Build] 전처리 단계 - 전처리기 지시문(preprocessor directive) (0) | 2025.04.14 |
---|---|
[C++][Build] 빌드와 빌드의 각 단계 (0) | 2025.04.14 |
[C++] 스마트 포인터(Smart Pointer) (0) | 2025.04.06 |
[C++][Callback] #4. 람다 표현식(Lambda Expression) (0) | 2025.04.03 |
[C++][Callback] #3. 함수 객체(functor) (0) | 2025.04.03 |