[C++] 범위와 수명
범위와 수명
범위(Scope)와 수명(Lifetime)은 정의된 변수를 어디에서 언제까지 사용할 수 있는가 입니다.
클래스, 함수 또는 변수와 같은 프로그램 요소를 선언하는 경우 해당 이름은 프로그램의 특정 부분에서만 “표시”되고 사용될 수 있습니다. 이름이 표시되는 컨텍스트를 해당 범위라고 합니다.
클래스, 함수, 변수와 같은 프로그램 요소를 선언하고 해당 이름(저장 공간)이 유효한 컨텍스트를 해당 범위(scope)라고 합니다.
변수는 수명(Life Time)을 가지는데, 변수의 메모리가 확보(Allocate)된 시점부터 저장 공간에서 할당 해제(Release)되고, 가용 메모리 풀(Memory pool)에 반환되기까지 메모리에 존재하는 시간을 말합니다.
범위
프로그램 내에서 동일한 변수의 이름이 존재해도 변수의 범위(변수가 선언된 블록)가 다르면 접근하는 저장 공간이 달라집니다.
C++ 프로그램 코드에서 가장 대표적인 코드의 범위는 중괄호({})
로 구분이 됩니다.
그러므로 동일한 범위 안에서는 동일한 이름의 변수가 두 번 선언될 수 없으나 새로운 범위 안에서는 같은 이름의 변수가 선언될 수 있습니다.
블록(Block)은 범위를 구분해 주는 단위로, 프로그램 문장들의 묶음이자 변수 선언이 가능한 공간입니다.
블록 안에서 선언된 변수를 지역변수(Local Variable) 그리고 어떤 블록에도 포함되지 않는 곳에서 선언된 변수를 전역변수(Global Variable)라고 합니다.
범위의 종류는 아래와 같습니다.
로컬 범위
매개 변수를 포함해 함수 또는 람다 내에 선언된 객체에는 로컬 범위가 있습니다.
객체가 선언 된 시점에서 생성됐다면 같은 블록의 끝까지 사용할 수 있습니다.
일반적으로 같은 블록의 범위를 벗어나면 해당 객체는 파괴됩니다.
#include <iostream>
int add(int x, int y) // 매개 변수 x, y는 여기서 생성된다.
{
// 변수 x와 y의 생성 이후 이 함수(main)에서 사용이 가능하다.
return x + y;
} // 변수 x와 y는 범위가 벗어나기 때문에 파괴된다.
int main()
{
int a = 1; // 변수 a는 여기서 생성되고 초기화 된다.
int b = 4; // 변수 b는 여기서 생성되고 초기화 된다.
// 변수 a와 b의 생성 이후 이 함수(main)에서 사용이 가능하다.
std::cout << add(a, b) << std::endl; // add함수를 호출하고 매개변수인 x와 y에 값이 대입된다.
{
int a = 0; // 여기의 a는 위의 a 변수와 다른 메모리를 가졌지만 같은 이름을 가진 변수 a입니다.
a = 3; // 여기서 호출한 a는 같은 범위의 a를 호출합니다.
} // 여기서 블록 안에 있는 a가 파괴됩니다.
// 여기의 변수 a는 호출되지 않아 값이 3이 아닌 1입니다.
return 0;
} // 변수 a와 b는 범위가 벗어나기 때문에 파괴된다.
int a = 0;
는 위에 있던 a
변수를 숨기게 되는데, 이를 이름 숨기기라고 합니다.
이 경우 a
를 호출하면 숨겨진 이름을 가진a
가 아닌 새롭게 생성된 변수 a
가 호출됩니다.
전역 범위
전역 범위는 클래스, 함수 또는 네임스페이스 외부의 범위를 말합니다.
전역 범위에서 생성된 변수들은 프로그램이 시작할 때 생성되고 프로그램이 종료될 때 파괴됩니다.
해당 범위에 존재하는 변수들은 암시적 전역 네임스페이스와 함께 존재합니다.
int a{ 1 }; // 전역 변수
static int b{ 4 }; // 전역 변수
extern int c{ 8 }; // 전역 변수
int add(int x, int y)
{
return x + y;
}
int main()
{
int a{ 0 }; // 전역 변수를 숨깁니다.
a++; // 지역 변수의 값이 증가합니다.
::a++; // 전역 변수의 값이 증가합니다.
add(b, c);
return 0;
} // 지역 변수 파괴
static
키워드를 사용한 내부 링크가 있는 전역 범위의 변수는 변수가 정의된 소스 파일 내의 어디든지 접근할 수 있지만 소스 파일 외부의 다른 파일에서는 접근하거나 참조할 수 없습니다.
이 경우 외부에서 사용하기 위해 extern
키워드를 사용해 외부 링크가 있는 전역 변수를 생성해 모든 소스 파일에서 접근이 가능하게 할 수 있습니다.
extern
키워드의 경우 전방 선언이 필요한데, 전방 선언을 할 때에도 같은 키워드를 사용해 선언합니다.
// 위의 cpp와 다른 파일이라고 가정
extern int c; // 전방 선언
함수의 경우 변수와 같은 링크 속성을 가지지만 항상 외부 링크가 기본 설정으로 돼있습니다.
내부 링크로 설정할 수 도 있는데 이 경우 static
키워드를 붙여줍니다.
static int add(int x, int y)
{
return x + y;
}
함수 범위
함수 내에서 선언 된 레이블은 해당 함수의 모든 위치, 모든 중첩 된 블록에서 자체 선언 전후에 적용됩니다.
void f()
{
{
goto label; // label은 나중에 선언되었지만 범위 안에 있다.
label:;
}
goto label; // label은 블록 범위를 무시한다.
}
void g()
{
goto label; // ERROR: label이 g() 내에서 범위에 있지 않다.
}
네임스페이스 범위
네임스페이스에 선언된 엔터티의 잠재적인 범위는 네임스페이스에 선언된 엔터티가 선언된 시점부터 시작해 이후 해당 네임스페이스의 모든 정의와 연결됩니다.
동일한 네임스페이스 이름에 대한 이후의 정의와 연결되고, using
지시문을 통해 다른 범위에 도입될 수도 있습니다.
번역 단위의 최상위 범위는 “파일 범위” 또는 “전역 범위” 라고 불리며, 이는 “전역 네임스페이스 범위”입니다.
전역 네임스페이스 범위에서 선언 된 엔터티의 잠재적 범위는 선언에서 시작하여 번역 단위 끝까지 계속됩니다.
명명되지 않은 네임스페이스 또는 인라인 네임스페이스에 선언된 엔터티의 범위에는 엔클로징 네임 스페이스가 포함됩니다.
namespace N { // N의 범위 시작 (전역 네임스페이스의 멤버)
int i;
int g(int a) { return a; }
int j();
void q();
namespace {
int x;
} // x의 범위는 끝나지 않는다.
inline namespace inl { // inl의 범위 시작
int y;
} // y의 범위는 끝나지 않는다.
} // i, g, j, q, inl, x, y는 범위를 벗어난다.
namespace {
int l=1;
} // l의 범위는 끝나지 않는다. (익명 네임스페이스의 멤버)
namespace N { // i, g, j, q, inl, x, y가 포함된다.
int g(char a) { // N::g(int a)를 오버로드
return l+a; // 익명 네임스페이스의 l이 범위 안에 있어서 가능하다.
}
int i; // 오류: 중복 정의 (i는 이미 범위 안에 있다)
int j(); // 허용됨: 함수 선언의 반복
int j() { // 이전에 선언된 N::j()의 정의
return g(i); // N::g(int a)를 호출
}
int q(); // 오류: q는 다른 반환 타입으로 이미 범위 안에 있다.
} // i, g, j, q, inl, x, y의 범위가 중단됨
int main() {
using namespace N; // i, g, j, q, inl, x, y의 범위가 재개된다.
i = 1; // N::i가 범위 안에 있다.
x = 1; // N::(익명)::x가 범위 안에 있다.
y = 1; // N::inl::y가 범위 안에 있다.
inl::y = 2; // N::inl도 범위 안에 있다.
} // i, g, j, q, inl, x, y의 범위가 중단됨
클래스 범위
클래스에 선언된 이름은 선언된 시점에서 시작해 클래스의 본문 내부 및 이것을 포함하는 범위에서 사용될 수 있습니다.
사용 될 때 클래스 본문의 나머지 부분과 모든 멤버 함수의 본문(클래스 정의 외부에 정의된 경우 포함), 기본 인수, 예외 사양이 포함되어 이름이 사용될 수 있습니다.
클래스 내 괄호 또는 동일한 이니셜라이저에 선언된 이름은 해당 괄호 또는 이니셜라이저에서의 범위에 포함됩니다.
이는 클래스 내부의 지역 변수 및 임시 개체 등을 의미합니다.
클래스 범위에 있는 멤버 이름의 접근성은 public
, protected
, private
키워드로 제어됩니다.
모든 클래스 멤버의 이름은 네 가지 컨텍스트에서만 사용할 수 있다.
- 자신의 클래스 범위 또는 파생 클래스의 클래스 범위 내에서
- 해당 클래스의 유형 또는 클래스에서 파생 된 클래스의 표현에 적용되는 연산자
->
연산자가 클래스에 대한 포인터 유형의 표현식에 적용된 후 또는 클래스에서 파생 된 클래스에 대한 포인터::
연산자가 해당 클래스의 이름 또는 클래스에서 파생 된 클래스의 이름에 적용
댓글남기기