이번 글에서는 최근 공부하면서 특히 질문이 많던 Scope, Lifetime, static 지역 변수, const, constexpr를 정리한다. 핵심 “이 변수를 어디에서 사용할 수 있는가?”, “언제까지 살아 있는가?”, “값 바꿀 수 있는가?”를 서로 다른 질문로 보는 것이다.
1.5 Scope and Lifetime — 보이는 범위와 살아 있는 시간은 다르다
Scope란 무엇인가
Scope 어떤 름을 드의 어디에서 사용할 수 있는지를 뜻한다. 지역 변수는 선언된 블록 안에서 이름을 사용할 수 있다.
void f()
{
int count = 10;
std::cout << count;
}
int main()
{
// std::cout << count; // compile error
}
count는 f() 의 로컬 변수이므로 main()에서 사용할 수 없다. 최근 문제에서도 “main scope에서 count는 사용할 수 없다”고 정리했다.
중첩된 블록의 Scope
int main()
{
int a = 10;
{
int b = 20;
// a: 사용 가능
// b: 사용 가능
}
// a: 사용 가능
// b: 사용 불가
}
안쪽 블록에서는 바깥 변수 a를 볼 수 있지만 바깥에는 안쪽에서 선언한 b를 볼 수 없다.
Lifetime이란 무엇인가
Lifetime은 객체가 실제로 존재하는 기간이다. 일반적인 지역 변수는 함수나 블록에 들어올 때 생성되고 그 블록을 벗어나면 수명이 끝난다.
void test()
{
int x = 10; // lifetime 시
} // lifetime 종료
static 지역 변수: Scope와 Lifetime을 분리해서 보기
void test()
{
static int count = 10;
++count;
std::cout << count << '\n';
}
count라는 이름은 test() 안에서만 보인다. 따라서 scope는 local이. 하지만 함수 호출이 끝나도 객체의 값은 유지된다.
test(); // 11
test(); // 12
test(); // 13
최근 학습에서 “static이니까 값이 유지되어 13이 된다”고 이해했다. 더 정확히 현하면 이름의 scope는 지역이지만 객의 lifetime은 계속 유된다.
static 지역 는 global 변수인가?
아니다. 함수 밖에서 이름을 사용할 수 없으므로 scope는 local이다. 다만 lifetime이 길다.
| 구분 | 일반 지역 변수 | static 지역 변수 |
|---|---|---|
| 이름을 볼 수 있는 범위 | 선언된 블록 | 선언된 블록 |
| 함수 종료 후 값 유지 | 아니오 | 예 |
| 다음 호출에서 이전 값 사용 | 아니오 | 예 |
임베디드 코드에서 static 지역 변수를 쓸 때
함수 내부 상태를 외부에 노출하 않으면서 호출 사에 값을 유지할 때 유용할 수 있. 지만 멀티스레드, ISR, 재진입 가능한 함수에서는 공유 상태처럼 동작할 수 있으므로 동시 근과 재진입성도 검토해야 한다.
1.6 Constants — 바뀌지 않는 값을 타입 시스템으로 표현기
const의 기본 의미
const int a{10};
// a = 20; // compile error
a는 성될 때 10으로 초기화되고 이후 다 값을 대입할 수 없다. 최근 문제에서 “이미 처음에 a가 10으로 초기화된 이후라서 변경할 없다”고 답했던 부분이 바로 이 개념이다.
const는 반드시 컴파일 타임 상수인가
그렇지 않다. const 객체는 런타임 값으로 초기될 수도 있다.
int read_value();
const int value = read_value();
read_value()의 결과는 실행 중에 결정될 수 있지만, 한 번 value가 초기화되고 나면 이후 수정할 수 없다.
constexpr는 무엇이 다른가
constexpr int buffer_size = 256;
int buffer[buffer_size];
constexpr는 해당 값이 상수 표현식으로 사용될 수 있음을 나타낸다. 컴파일 타임 계산이 필요한 곳에 적합하다.
| 구분 | const | constexpr |
|---|---|---|
| 초기화 후 변경 | 불가 | 불가 |
| 런타임 값로 초기화 | 가능한 경우 있 | 일반적으 불가 |
| 컴파일 타임 상 표현식 | 항상 보장하지 않음 | 주요 목적 |
const와 초기화의 관계
const int max_retry{3};
const 객체는 나중에 값을 넣는 것이 아니라 선언할 때 적절한 값으로 초기화하는 것이 핵심이다.
임베디드에서 const / constexpr가 유용한 곳
constexpr std::uint32_t STATUS_REG = 0x4000020;
constexpr std::uint16_t CAN_PERIOD_MS = 250;
constexpr std::size_t RX_BUFFER_SIZE = 256;
레지스터 주소, CAN 주기, 고정 퍼 크기처럼 실행 바뀔 이유가 없는 값은 상수로 표현하는 편이 읽기 쉽고 안전하다. 숫자를 코드 곳곳 직 쓰는 것보다 이름과 단위를 함께 드러 수 있다.
상수를 쓰는 이유는 변 방지뿐인가
아니다. 상수는 코드 리뷰와 유지보수에도 도움을 준다. 어떤 값이 상태인지, 어떤 값이 설정인지, 어떤 값이 절대 바뀌면 안 되는 규칙인지 구분하기 쉬워진다.
1.5 1.6을 함께 보면
- 이 변수의 타입은 무엇인가?
- 이 이름은 어디까지 보이는가? → Scope
- 객체는 언제까지 살아 있는가? → Lifetime
- 초기화 후 값을 바꿀 수 있는가? → const / constexpr
이 네 가지를 함께 보면 실무 C++ 코드의 상태 변화가 훨씬 선명하게 보다.
마무리
1.5~1.6은 문법 자체보다 변수의 존재 방식과 상태를 이해하는 챕터라고 느꼈다. static 지역 변수를 단순히 “전역처럼 값이 남는 변수”라고 외우기보다 local scope와 긴 lifetime을 분리해 이해하는 것이 중하다. const와 constexpr도 단순 키워드가 아니라 변경 가능성과 컴파일 타임 의도를 코드에 표현하는 수단이다.
1.5 Scope and Lifetime
Scope describes where a name can be used. Lifetime describes how long the object itself exists. A normal local variable usually disappears when its block ends.
Static local variables
A static local variable still has local scope, but its stored value survives after the function returns. It is not the same as a global variable because the name remains local to the function.
1.6 Constants
const prevents modification after initialization, while constexpr is intended for values usable in compile-time constant expressions.
const int a{10};
constexpr int buffer_size = 256;Embedded perspective
Register addresses, protocol constants, timing values, and fixed buffer sizes are natural candidates for named constants. They make hardware-facing code easier to review and reduce accidental modification.
Takeaway
When reading a variable, ask four questions: what is its type, where is its name visible, how long does the object exist, and can its value change?
'C++' 카테고리의 다른 글
| C++ 1.8 Tests 정리: 조건식, short-circuit, if 초기화, switch fall-through, vector::size() (0) | 2026.09.14 |
|---|---|
| C++ 1.7 포인터와 레퍼런스 정리: 주소, 배열, 참조, nullptr까지 (0) | 2026.09.13 |
| C++ std::variant, 여러 타입 중 하나를 담는 법 (0) | 2026.09.13 |
| C++ 기초 다시 공부하기 ① 1.1~1.4.2 프로그램, 함수, 타입과 초기화 (0) | 2026.09.12 |
| C++ std::format, 포맷 문자열로 출력하는 법 (0) | 2026.09.11 |
