
제목에 언급한 변수 및 연결을 알아보기 전에 메모리 구조부터 이해하자
C++ 프로그램이 실행되면 메모리는 크게 4구역으로 나뉜다
| 코드 영역 | 실행할 명령어들 |
| 데이터 영역 | 전역 변수, static 변수 (프로그램 시작~종료까지 유지) |
| 힙 영역 | new로 동적 할당 |
| 스택 영역 | 지역 변수 (함수 호출~종료까지만 존재) |
1. 지역 변수 (Local Variable)
void foo() {
int x = 10; // 스택에 생성
// foo() 끝나면 x 소멸
}
- 생존 기간: 함수 호출 시 생성, 함수 종료 시 소멸
- 접근 범위: 해당 함수 안에서만
2. 전역 변수 (Global Variable)
// a.cpp
int g_count = 0; // 함수 밖에 선언 -> 데이터 영역에 생성
void add() { g_count++; }
void print() { cout << g_count; } // 어디서든 접근 가능
// b.cpp
extern int g_count;
g_count++; // 다른 cpp 파일에서도 접근 가능
- 생존 기간: 프로그램 시작부터 종료까지
- 접근 범위: 프로그램 전체
단점: 어디서든 수정 가능하기 때문에 버그 추적이 어렵다. 실무에서는 꼭 필요한 경우에만 사용한다.
3. static 변수 - 핵심 개념
static은 "딱 한 번만 초기화하고, 프로그램이 끝날 때까지 유지" 라는 의미입니다
void foo() {
int local = 0; // 호출마다 새로 생성, 소멸
static int stat = 0; // 딱 한 번만 초기화, 이후 호출엔 이전 값 유지
local++;
stat++;
cout << "local: " << local << ", static: " << stat << endl;
}
foo(); // local: 1, static: 1
foo(); // local: 1, static: 2 <- stat은 이전 값을 기억
foo(); // local: 1, static: 3
비유: local은 배번 새 종이에 쓰고 버리는 메모지, static은 계속 누적해서 쓰는 노트
- 저장 위치는 데이터 영역 (전역 변수와 동일)
- 하지만 접근 범위는 여전히 그 함수 안에서만
4. 내부 연결 vs 외부 연결
여기서 "연결(linkage)"이란 "이 변수/함수를 다른 파일에서도 쓸 수 있는가?"를 의미한다
내부 연결 (Internal Linkage)
이 파일 안에서만 사용 가능
// a.cpp
static int x = 10; // static -> 내부 연결, a.cpp 안에서만 존재
// 또는
const int y = 20; // const도 기본적으로 내부 연결
void printA() {
x++;
cout << "a.cpp x: " << x << endl;
}
a.cpp의 x는 a.cpp에서만 보이고 사용할 수 있다
b.cpp의 x는 b.cpp에서만 보임 (완전히 별개의 본사본)
외부 연결 (External Linkage)
다른 파일에서도 함께 사용 가능
// a.cpp
int x = 10; // 일반 전역 변수 -> 외부 연결
extern const int y = 20; // extern -> 외부 연결
void printA() {
x++;
cout << "a.cpp x: " << x << endl;
}
// b.cpp
extern int x; // a.cpp의 x를 가져다 씀
void printB(){
x++;
cout << "b.cpp x: " << x << endl;
}
// main.cpp
int main() {
printA(); // a.cpp x: 11
printA(); // a.cpp x: 12
printB(); // b.cpp x: 13 <- 같은 x를 공유하기 때문
printB(); // b.cpp x: 14
}
// ---
내부 연결 (static) 외부 연결 (extern)
a.cpp → [x: 10] a.cpp ──┐
├── [x: 10]
b.cpp → [x: 10] b.cpp ──┘
(각자 따로) (하나를 공유)
5. 실전 - 헤더로 상수 공유하기
방법 A: 헤더에 직접 정의 (내부 연결)
// game_config.h
#pragma once
namespace Config {
const int MAX_PLAYERS = 4; // include한 파일마다 복사본 생성
const float GRAVITY = 9.8f; // 파일이 많아질수록 메모리 낭비
}
// physics.cpp
#include "game_config.h"
#include <iostream>
void applyGravity() {
// physics.cpp만의 GRAVITY 복사본 사용
std::cout << "Physics GRAVITY 주소: " << &Config::GRAVITY << std::endl;
}
// main.cpp
#include "game_config.h"
#include <iostream>
int main() {
// main.cpp만의 GRAVITY 본사본 사용
std::cout << "Main GRAVITY 주소 : " << &Config::GRAVITY << std::endl;
applyGravity();
// 출력
// Main GRAVITY 주소: 0x100 <- 서로 다름
// Physics GRAVITY 주소: 0x200 <- 서로 다름
}
방법 B: extern + .cpp 분리 (외부 연결)
실무에서 방법 B를 쓰는 이유:
- 설정 값이 많아질수록 방법 A는 파일 수 만큼 복사본이 생겨 메모리 낭비
- 방법 B는 game_config.cpp 한 곳만 수정하면 전체에 반영
- 큰 프로젝트(파일 수십 개)에서 차이가 두드러진다
// game_config.h
#pragma once
namespace Config {
extern const int MAX_PLAYERS; // 선언만 -> "이 변수는 다른 파일에 정의되어 있다"
extern const float GRAVITY; // extern만 붙이면 const도 외부 연결로 바뀜
}
// game_config.cpp
#include "game_config.h"
namespace Config
extern const int MAX_PLAYERS = 4; // 실제 정의 -> 메모리에 딱 한 번만 생성
extern const float GRAVITY = 9.8; // 모든 파일이 이 하나의 변수를 공유
}
// physics.cpp
#include "game_config.h" // 선언만 가져옴, 정의는 game_config.cpp에 있음
#include <iostream>
void applyGravity() {
// game_config.cpp의 GRAVITY를 참조 → main.cpp와 주소 동일
std::cout << "Physics GRAVITY 주소: " << &Config::GRAVITY << std::endl;
}
// main.cpp
#include "game_config.h"
#include <iostream>
int main() {
// game_config.cpp의 GRAVITY를 참조 → physics.cpp와 주소 동일
std::cout << "Main GRAVITY 주소: " << &Config::GRAVITY << std::endl;
applyGravity();
// 출력
// Main GRAVITY 주소: 0x100 <- 같음
// Physics GRAVITY 주소: 0x100 <- 같음
}
댓글