10. C++ 범위(C++ Scope)
1. 키워드
- 유효 범위(Scope)
- 연결(Linkage)
- 자동 변수(Automatic Variable)와 지역 변수(Local Variable)
- 레지스터 변수(Register Variable)와
register
키워드 - 정적 변수(Static Variable)
- 연결을 가지지 않는 정적 변수와
static
키워드 - 내부 연결을 가지는 정적 변수와
static
키워드 - 외부 연결을 가지는 정적 변수와
extern
키워드 - 네임스페이스(Namespace)와
namespace
키워드 ::
(범위 지정 연산자),using
지시자(Directive),using
선언(Declaration)
2. 유효 범위와 연결
- 유효 범위란 하나의 변환 단위(Translation Unit) 내에서 해당 변수가 사용될 수 있는 범위를 나타낸다.
- 함수 내에서 선언된 변수는 함수 내에서만 사용할 수 있다.
- 또한, 함수 밖에서 선언된 변수는 변수가 선언된 이후에 나오는 모든 함수에서 사용할 수 있다.
- C++ 표준에서는 파일(File)이라는 용어 대신에 변환 단위라는 용어를 사용하고 있다.
- 연결이란 해당 변수를 사용할 수 있는 파일의 접근 가능 여부를 나타낸다.
- 외부 연결(External Linkage)을 가지는 변수는 여러 파일에서 사용할 수 있다.
- 하지만 내부 연결(Internal Linkage)을 가지는 변수는 하나의 파일에서만 사용할 수 있다.
- 또한, 함수 내에서 선언된 변수는 함수 밖에서는 사용할 수 없으므로 연결을 가지지 않는다.
- C++에서 변수는 유효 범위, 연결 등을 기준으로 다음과 같이 나눌 수 있다.
1] 자동 변수
2] 레지스터 변수
3] 정적 변수
1) 자동 변수
- 자동 변수란 블록 내에서 선언된 변수를 의미하며, 함수의 매개변수도 여기에 포함된다.
- 이러한 자동 변수는 변수가 선언될 블록 내에서만 유효하며, 블록이 종료되면 메모리에서 사라진다.
- 자동 변수는 메모리 상의 스택(Stack) 영역에 저장되며, 초기화하지 않으면 쓰레깃값으로 초기화된다.
- 이러한 자동 변수는 C의 지역 변수와 같은 의미로 사용된다.
#include <iostream>
using namespace std;
void Local(void);
int main(void) {
int i = 5;
int var = 10;
cout << "main() 함수 내의 자동 변수 var의 값은 "
<< var << "입니다." << endl;
if (i < 10) {
Local();
int var = 30;
cout << "if 문 내의 자동 변수 var의 값은 "
<< var << "입니다." << endl;
}
cout << "현재 자동 변수 var의 값은 "
<< var << "입니다." << endl;
return 0;
}
void Local(void) {
int var = 20;
cout << "Local() 함수 내의 자동 변수 var의 값은 "
<< var << "입니다." << endl;
}
// main() 함수 내의 자동 변수 var의 값은 10입니다.
// Local() 함수 내의 자동 변수 var의 값은 20입니다.
// if 문 내의 자동 변수 var의 값은 30입니다.
// 현재 자동 변수 var의 값은 10입니다.
- 위의 코드에서 변수
var
는 한 번은main()
함수 내에서, 또 한 번은if
문에서, 마지막은Local()
함수 내에서 선언된다. - 이처럼 같은 이름의 변수
var
는 모두 다른{}
(중괄호) 영역에서 선언되었으며, 이러한{}
영역을 블록(Block)이라고 한다. - 이렇게 변수의 유효 범위는 변수가 선언된 블록을 기준으로 설정되며, 해당 블록이 끝나면 모든 자동 변수는 메모리에서 사라지게 된다.
- 위의 코드에서처럼 변수의 이름으로 같은 이름을 여러 번 사용하는 것은 매우 잘못된 방법이다.
- 그리고 한 블록 내에서 같은 이름의 변수를 또다시 선언하려고 하면 컴파일러는 오류를 발생시킨다.
2) 레지스터 변수
- 레지스터 변수란 변수를 선언할 때
register
키워드를 붙여 선언한 변수를 의미한다. - C에서 이렇게 선언된 레지스터 변수는 CPU의 레지스터(Register) 메모리에 저장되어 빠르게 접근할 수 있게 된다.
- 하지만 컴퓨터의 레지스터는 매우 작은 크기의 메모리이므로, 이 영역에 변수를 선언하기 힘든 경우도 많다.
- 그럴 때 C 컴파일러는 해당 변수를 그냥 지역 변수로 선언하게 된다.
- C++11 이전에는 C++에서도
register
키워드가 위와 같은 의미로 사용되었다. - 하지만 C++11부터는 단순히 해당 변수가 자동 변수라는 것을 의미하는 역할로만 사용되고 있다.
3) 정적 변수
- C++에서 정적 변수는 프로그램이 실행되는 내내 유지되는 변수를 의미한다.
- 이러한 정적 변수는 메모리 상의 데이터(Data) 영역에 저장되며, 초기화하지 않으면
0
으로 자동 초기화된다. - C++에서는 다음과 같이 세 가지 유형의 정적 변수를 제공하고 있다.
1] 연결을 가지지 않는 정적 변수
2] 내부 연결을 가지는 정적 변수
3] 외부 연결을 가지는 정적 변수
(1) 연결을 가지지 않는 정적 변수
- 연결을 가지지 않는 정적 변수는 블록 내부에서
static
키워드를 사용하여 정의한다. - 이러한 연결을 가지지 않는 정적 변수는 지역 변수와 전역 변수의 특징을 모두 가지게 된다.
- 이 변수는 전역 변수처럼 단 한 번만 초기화되며, 프로그램이 종료되어야 메모리 상에서 사라진다.
- 또한, 이렇게 선언된 변수는 지역 변수처럼 해당 블록 내에서만 접근할 수 있다.
#include <iostream>
using namespace std;
void Local(void);
void StaticVar(void);
int main(void) {
for (int i = 0; i < 3; i++)
Local();
for (int i = 0; i < 3; i++)
StaticVar();
return 0;
}
void Local(void) {
int count = 1;
cout << "Local() 함수가 "
<< count << "번째 호출되었습니다." << endl;
count++;
}
void StaticVar(void) {
static int static_count = 1; // 연결을 가지지 않는 정적 변수
cout << "StaticVar() 함수가 "
<< static_count << "번째 호출되었습니다." << endl;
static_count++;
}
// Local() 함수가 1번째 호출되었습니다.
// Local() 함수가 1번째 호출되었습니다.
// Local() 함수가 1번째 호출되었습니다.
// StaticVar() 함수가 1번째 호출되었습니다.
// StaticVar() 함수가 2번째 호출되었습니다.
// StaticVar() 함수가 3번째 호출되었습니다.
- 위의 코드는 지역 변수로 선언된
count
와 연결을 가지지 않는 정적 변수로 선언된static_count
를 서로 비교하는 것이다. - 지역 변수인
count
는 함수의 호출이 끝날 때마다 메모리 상에서 사라진다. - 하지만 연결을 가지지 않는 정적 변수인
static_count
는 함수의 호출이 끝나도 메모리 상에서 사라지지 않는다. - 그러므로 다음 함수 호출 때에도 이전의 데이터를 그대로 저장하고 있다.
- 또한,
static_count
는 전역 변수와는 달리 자신이 선언된StaticVar()
함수 이외의 영역에서는 호출할 수 없다.
(2) 내부 연결을 가지는 정적 변수
- 내부 연결을 가지는 정적 변수는 유효 범위를 변환 단위로 가지는 변수에
static
키워드를 사용하여 정의한다. - 이러한 내부 연결을 가지는 정적 변수는 해당 변수를 포함하고 있는 변환 단위, 즉 현재 파일에서만 사용할 수 있다.
- 이 변수는 하나의 파일 내의 모든 블록에서 접근할 수 있고, 또한 사용할 수 있다.
#include <iostream>
using namespace std;
static int var; // 내부 연결을 가지는 정적 변수
void Local(void);
int main(void) {
cout << "변수 var의 초깃값은 " << var << "입니다." << endl;
int i = 5;
int var = 10; // 자동 변수 선언
cout << "main() 함수 내의 자동 변수 var의 값은 "
<< var << "입니다." << endl;
if (i < 10) {
Local();
cout << "현재 변수 var의 값은 "
<< var << "입니다." << endl; // 자동 변수에 접근
}
cout << "더 이상 main() 함수에서는 정적 변수 var에 접근할 수 없습니다." << endl;
return 0;
}
void Local(void) {
var = 20; // 정적 변수의 값 변경
cout << "Local() 함수 내에서 접근한 정적 변수 var의 값은 "
<< var << "입니다." << endl;
}
// 변수 var의 초깃값은 0입니다.
// main() 함수 내의 자동 변수 var의 값은 10입니다.
// Local() 함수 내에서 접근한 정적 변수 var의 값은 20입니다.
// 현재 변수 var의 값은 10입니다.
// 더 이상 main() 함수에서는 정적 변수 var에 접근할 수 없습니다.
- 위의 코드에서 정적 변수
var
와 같은 이름의 자동 변수var
가main()
함수 내부에서 선언된다. - 이 자동 변수가 선언되기 전까지
main()
함수에서도 정적 변수var
에 접근할 수 있다. - 하지만 자동 변수
var
가 선언된 후에는main()
함수에서 정적 변수var
로 접근할 방법이 없어진다. - 왜냐하면, 블록 내에서 선언되 자동 변수는 같은 이름의 정적 변수를 덮어쓰기 때문이다.
- C++에서는 하나의 파일 내의 모든 블록에서 데이터를 공유하기 위한 또 다른 방법으로 네임스페이스를 제공하고 있다.
(3) 외부 연결을 가지는 정적 변수
- 외부 연결을 가지는 정적 변수는 유효 범위를 변환 단위로 가지는 변수를 의미한다.
- 이러한 외부 연결을 가지는 정적 변수를 전역 변수(Global Variable) 또는 외부 변수(Extern Variable)라고 한다.
- 외부 변수는 해당 파일뿐만 아니라 외부 파일에서도 사용할 수 있는 변수이다.
- 이러한 외부 변수는 해당 변수를 사용하는 모든 파일에서 각각
extern
키워드를 사용하여 재선언되어야 사용할 수 있다.
// 파일2.cpp
#include <iostream>
using namespace std;
#include "파일1.cpp"
extern int var; // 외부 변수를 사용하기 위한 재선언
...
- C++에서는 여러 파일에서 외부 변수가 사용될 경우, 오직 한 개의 파일에서만 외부 변수에 대한 정의를 할 수 있다.
- 이러한 원칙을 단일 정의의 원칙이라고 한다.
4) 변수의 종류
변수 종류 | 유효 범위 | 연결 | 선언 위치 | 키워드 |
---|---|---|---|---|
자동 변수 | 블록 | 없음 | 블록 내부 | 없음 |
레지스터 변수 | 블록 | 없음 | 블록 내부 | register |
연결을 가지지 않는 정적 변수 | 블록 | 없음 | 블록 내부 | static |
내부 연결을 가지는 정적 변수 | 변환 단위 | 내부 | 함수 외부 | static |
외부 연결을 가지는 정적 변수 | 변환 단위 | 외부 | 함수 외부 | 없음 |
3. 네임스페이스
- C++에서는 변수, 함수, 구조체, 클래스 등을 서로 구분하기 위해서 이름으로 사용되는 다양한 내부 식별자(Identifier)를 가지고 있다.
- 하지만 프로그램이 복잡해지고 여러 라이브러리가 포함될수록 내부 식별자 간에 충돌할 가능성도 그만큼 커진다.
- 이러한 이름 충돌 문제를 C++에서는 네임스페이스를 통해 해결하고 있다.
- C++에서 네임스페이스란 내부 식별자에 사용될 수 있는 유효 범위를 제공하는 선언적 영역을 의미한다.
1) 네임스페이스의 정의
- C++에서는
namespace
키워드를 사용하여 사용자가 새로운 네임스페이스를 정의할 수 있다. - 이러한 네임스페이스는 전역 위치뿐만 아니라 다른 네임스페이스 내에서도 정의될 수 있다.
- 하지만 블록 내에서는 정의될 수 없으며, 기본적으로 외부 연결을 가지게 된다.
- 일반적으로 네임스페이스는 헤더 파일에서 정의되며, 언제나 새로운 이름을 추가할 수 있도록 개방되어 있다.
// namespace.h
namespace kang
{
void Display(); // 함수의 원형
int count; // 변수의 선언
}
namespace kim
{
double display; // 변수의 선언
int count; // 변수의 선언
}
- C++에서는 전역 네임스페이스라고 하는 파일 수준의 선언 영역이 존재한다.
- 일반적으로 식별자의 네임스페이스가 명시되지 않으면, 전역 네임스페이스에 자동으로 포함되게 된다.
- 또한, C++ 표준 라이브러리 타입과 함수들은
std
네임스페이스 또는 그 속에 중첩된 네임스페이스에 선언되어 있다.
2) 네임스페이스로의 접근
- 네임스페이스를 정의한 후에는 해당 네임스페이스로 접근할 수 있는 방법이 필요하다.
- 네임스페이스에 접근하기 위해서는
::
(범위 지정 연산자)를 사용하여, 해당 이름을 특정 네임스페이스로 제한하면 된다.
#include "namespace.h"
...
kang::count = 4; // 네임스페이스 kang에 접근하여 count 변수 사용
kim::display = 3.14; // 네임스페이스 kim에 접근하여 display 변수 사용
kim::count = 100; // 네임스페이스 kim에 접근하여 count 변수 사용
...
3) 간소화된 네임스페이스로의 접근
- 네임스페이스에 속한 이름(변수, 함수, 구조체, 클래스 등)을 사용할 때마다
::
를 사용하여 이름을 제한하는 것은 매우 불편하다. - 또한, 길어진 코드로 인해 가독성 또한 떨어지게 된다.
- C++에서는 이러한 불편함을 해소할 수 있도록 다음과 같은 방법을 제공하고 있다.
1] using
지시자
2] using
선언
(1) using
지시자
using
지시자는 명시한 네임스페이스에 속한 이름을 모두 가져와::
를 사용하지 않고도 사용할 수 있게 해준다.- 전역 범위에서 사용된
using
지시자는 해당 네임스페이스의 모든 이름을 전역적으로 사용할 수 있게 만들어준다. - 또한, 블록 내에서 사용된
using
지시자는 해당 블록에서만 해당 네임스페이스의 모든 이름을 사용할 수 있게 해준다.
- C++에서
using
지시자를 사용하는 문법은 다음과 같다.
(2) using
선언
using
지시자가 명시한 네임스페이스의 모든 이름을 사용할 수 있게 했다면,using
선언은 단 하나의 이름만을::
를 사용하지 않고도 사용할 수 있게 해준다.- 또한,
using
지시자와 마찬가지로using
선언이 나타나는 선언 영역에서만 해당 이름을 사용할 수 있게 해준다.
- C++에서
using
선언을 사용하는 문법은 다음과 같다.