스코프 (Scope)
변수의 영역 또는 스코프(Scope)란 프로그램에서 변수를 사용할 수 있는 범위를 의미한다. 변수는 자신의 영역 안에서만 값을 대입하거나 값을 읽어올 수 있다. 변수가 선언되는 곳이 바로 영역이 시작되는 곳이다.
// 중괄호를 이용하여 블록을 나타낼 수 있다.
{
int x12;
x12 = 2 + 5 * 2;
}
블록(Block)은 영역을 구분해 주는 단위로, 프로그램 문장들의 묶음이자 변수 선언이 가능한 공간이다. 블록 안에서 선언된 변수를 지역변수(Local Variable), 블록 밖에서 선언되었으나 블록 안에서 사용될 수 있는 변수를 비지역변수(Non-Local Variable)이라고 한다. 그리고 어떤 블록에도 포함되지 않는 곳에서 선언된 변수를 전역변수(Global Variable)이라고 한다.
{
// 참조환경: 지역변수 b
int b;
b = 1;
{
// 참조환경: 지역변수 c, 비지역변수 b
int c; // C는 지역변수
c = b * 2; // 이때 b는 비지역변수이다.
}
}
{
// 참조환경: 지역변수 d
int d;
}
위의 코드를 보면 알 수 있듯, 지역변수와 비지역변수는 서로 상대적인 개념이다. 프로그램의 위치마다 사용할 수 있는 변수는 다를 수 있다. 한 위치에서 사용할 수 있는 모든 변수의 모음을 "참조 환경(Referencing Environment)"라고 한다. 특정 위치의 참조환경은 해당 위치의 모든 지역변수와 비지역변수로 구성된다.
영역 규칙 (Scope Rule)
영역 규칙(Scope Rule)이란 변수의 참조 위치를 결정하는 방법이다. 특별히 비지역변수의 위치를 어떻게 결정할 것인지를 주로 다룬다. 현재 블록에서 선언되지 않고 사용하려는 변수를 "자유변수(Free Variable)"이라고 한다. 영역 규칙에 따라 참조 위치를 결정하게 되는데, 참조 위치를 찾은 경우에는 비지역변수로 결정되고 참조 위치를 찾지 못하면 오류로 판단된다. 영역 규칙의 종류로는 정적 영역 규칙과 동적 영역 규칙이 있다.
정적 영역 규칙 (Static Scope Rule)
정적 영역 규칙은 블록들의 포함 관계를 "문맥적으로" 판단하는 정적 내포 관계를 이용하여 변수의 참조 위치를 찾는 방법이다. 프로그램 수행 없이 내포 관계를 판단할 수 있기에 정적이라는 표현을 사용한다. 정적 영역 규칙은 "사전적 영역 규칙(Lexical Scope Rule)"이라고도 불린다. 정적 영역 규칙의 방법은 다음과 같다.
- 사용하려는 변수의 이름에 대한 선언이 현재 블록 안에 있다면, 그 지역변수를 참조한다.
- 지역변수가 아닌 자유변수라면, 현재 블록의 정적 부모에 대해 자유변수 이름에 대한 선언이 있는지 확인한다.
- 선언이 있다면 그 변수를 비지역변수로 참조한다.
- 선언이 없다면 그 블록의 정적 부모에 대해 선언이 있는지 확인하는 작업을 반복한다.
- 확인할 블록이 없을 때까지(최외곽 영역까지), 선언을 찾지 못하는 경우 잘못된 변수 참조이기에 오류로 판단한다.
이때, 비지역변수가 같은 이름의 지역변수로 인해 생기는 보이지 않는 영역인 "영역 구멍(Scope Hole)"이 생길 수 있다.
정적 영역 규칙에서 사용되는 필수 용어
- 정적 조상(Static Ancestors): 블록을 문맥적으로 포함하는 모든 블록
- 정적 부모(Static Parent): 특정 블록에서 가장 가까운 정적 조상
동적 영역 규칙 (Dynamic Scope Rule)
동적 영역 규칙은 블록들의 포함 관계를 "서브프로그램의 호출 관계로" 판단하는 동적 내포 관계를 이용하여 변수의 참조 위치를 찾는 방법이다. 동일한 서브프로그램이라도 누가 호출했는가에 따라 포함 관계는 달라질 수 있기에 프로그램 수행 시점에서만 판단할 수 있다. 동적 영역 규칙의 방법은 다음과 같다.
- 사용하려는 변수의 이름에 대한 선언이 현재 블록 안에 있다면, 그 지역변수를 참조한다.
- 지역변수가 아닌 자유변수라면, 현재 블록을 호출한 블록에 대해 자유변수 이름에 대해 선언이 있는지 확인한다.
- 선언이 있다면 그 변수를 비지역변수로 참조한다.
- 선언이 없다면 그 블록을 호출한 블록에 대해 선언이 있는지 확인하는 작업을 반복한다.
- 확인할 블록이 없을 때까지(최초호출자 영역까지), 선언을 찾지 못하는 경우 잘못된 변수 참조이기에 오류로 판단한다
영역 규칙의 비교
- 정적 영역 규칙: 컴파일 시점에 변수의 참조 위치를 결정하고, 정적 타입 검사가 가능하며 빠른 수행 속도를 가진다.
- 동적 영역 규칙: 수행 시점에 변수의 참조 위치를 결정하고, 정적 타입 검사가 불가능하며 수행 속도가 느리다.
네임스페이스 (Namespace)
전역 변수는 어떤 블록에도 포함되지 않는 곳에서 선언된 변수이며, 프로그램 전체가 영역이 된다. 모든 블록에서 비지역변수로 취급된다. 전역변수의 영역은 프로그램 전체이지만 그럼에도 영역 구멍이 발생할 수 있는데, 이때 영역 연산자(::)를 사용하여 영역 구멍에서도 전역변수를 볼 수 있다. 아래의 c++ 예시 코드를 통하여 이해해보도록 하자.
#include <iostream>
int i = 0;
int main() {
int i = 5;
i++;
::i++;
cout << "local i =" << i << endl;
cout << "nonlocal i =" << ::i << endl;
return 0;
}
// 실행 결과
// local i = 6
// nonlocal i = 1
영역 연산자를 이용하여 영역 구멍에서도 전역변수를 사용할 수 있는 것처럼, 이름 공간을 사용하여 영역이 아닌 곳에서 변수를 사용할 수 있다.
이름 공간(Namespace)이란 관련성이 높은 변수와 함수를 하나의 묶음으로 관리하는 영역으로 변수명처럼 영역 자체의 이름을 가진다. 이름 공간 내의 변수 또는 함수를 이름 공간 밖에서 사용하려면, 예약어 using과 이름공간의 이름, 영역 연산자 ::를 사용하면 된다.
#include <iostream>
using namespace std;
namespace s1 {
int k = 0;
void Output() {
cout << "k = " << k << endl;
}
}
int main() {
// 이름공간의 이름과 영역 연산자 ::를 이용한다.
s1::k++;
s1::Output();
// using 키워드를 사용한 이후로는 이름공간의 이름과 영역 연산자 생략 가능
using namespace s1;
k++;
Output();
return 0;
}
// 실행 결과
// k = 1;
// k = 2;