프로그래밍언어론 8강 - 변수의 영역과 이름공간
프로그램의 각 위치에서 어떤 변수를 사용할 수 있는지 결정하는 영역의 개념을 학습한다. 블록·참조환경·자유변수의 관계를 바탕으로 정적 영역 규칙과 동적 영역 규칙을 비교하고, 영역 구멍과 전역변수의 문제를 C++의 영역 연산자와 이름공간으로 해결하는 방법을 살펴본다.
제1장 변수의 영역과 수명
1. 영역의 의미
변수의 영역(scope)은 프로그램에서 그 변수에 값을 대입하거나 변수의 값을 읽어 올 수 있는 범위이다. 변수는 선언된 뒤부터 무조건 프로그램 전체에서 사용할 수 있는 것이 아니다. 어느 위치에서 선언되었는지, 그 선언을 둘러싼 블록이 어디까지인지, 언어가 어떤 영역 규칙을 채택했는지에 따라 접근할 수 있는 범위가 달라진다.
C 언어의 블록 안에서 int x12;를 선언했다면 일반적으로 그 선언문부터 블록을 닫는 중괄호까지가 x12의 영역이다. 선언보다 앞에서 x12 = 0;을 실행하거나 블록이 끝난 뒤 x12를 참조하면 해당 위치에는 사용할 수 있는 선언이 없으므로 오류가 발생한다. 반면 Python 예처럼 블록 구조와 선언 처리 방식이 다른 언어에서는 같은 모양의 코드가 다르게 동작할 수 있다. 따라서 영역은 언어의 규칙과 프로그램의 구조를 함께 보아야 한다.
2. 영역과 수명의 차이
수명(lifetime)은 실행 중 변수가 기억장소를 차지하고 존재하는 시간이다. 영역이 프로그램 문장상의 공간적 범위라면 수명은 프로그램 실행상의 시간적 범위이다. 두 개념은 관련이 있지만 같지 않다.
강의의 Python 예에서는 바깥에서 정의된 x12가 프로그램 전체에서 사용될 수 있고 수명도 프로그램 전체에 걸친다. 함수 안에서 만들어진 별도의 x12는 함수 내부가 영역이며, 함수가 호출되어 실행되는 동안으로 수명이 한정된다. 이름이 같아도 선언 위치에 따라 서로 다른 변수가 되며, 각각의 영역과 수명을 따로 가진다.
| 구분 | 영역 | 수명 |
|---|---|---|
| 관점 | 프로그램의 위치 | 프로그램 수행 시간 |
| 질문 | 어디에서 변수 이름을 사용할 수 있는가? | 언제 변수가 존재하는가? |
| 함수 내부 변수의 예 | 함수 내부 | 함수가 호출되어 활동하는 동안 |
3. 블록과 언어별 표현
블록(block)은 프로그램 문장들의 묶음이며 영역을 구분하는 단위로 사용된다. 블록 안에서는 변수를 선언할 수 있고, 그 변수의 영역은 선언된 위치에서 시작해 보통 블록이 끝나는 지점까지 이어진다. C 계열 언어에서는 중괄호 { }가 대표적인 블록 경계를 만든다.
다만 블록의 표현과 적용 기준은 언어마다 다르다. Algol 60에서는 begin부터 end까지의 복합문이 블록이다. C, C++, Java에서는 중괄호 복합문뿐 아니라 함수와 클래스도 블록으로 취급된다. Pascal에서는 begin과 end로 묶인 복합문 자체가 블록인 것은 아니고 주프로그램과 서브프로그램이 블록이 된다. 겉모양이 비슷하더라도 언어별 블록 정의를 구분해야 한다.
제2장 지역변수와 참조환경
1. 지역변수와 비지역변수
지역변수(local variable)는 해당 블록 안에서 선언되어 사용되는 변수이다. 비지역변수(non-local variable)는 프로그램의 특정 위치에서 사용할 수 있지만 그 위치를 포함하는 현재 블록에서는 선언되지 않은 변수이다. 즉, 현재 블록 바깥의 유효한 선언을 참조하는 변수이다.
지역과 비지역은 절대적인 분류가 아니라 어느 블록에서 바라보는가에 따라 달라지는 상대적 개념이다. 바깥 블록 A에 선언된 b는 A에서는 지역변수이다. A 안의 중첩 블록 B가 b를 사용하면 B에서 직접 선언한 것이 아니므로 B의 관점에서는 비지역변수이다. 반대로 B에서 선언된 c는 B의 지역변수이며 B 밖에서는 일반적으로 사용할 수 없다.
| 판정 기준 | 지역변수 | 비지역변수 |
|---|---|---|
| 선언 위치 | 현재 블록 안 | 현재 블록 밖의 접근 가능한 영역 |
| 관점 | 같은 변수도 바라보는 블록에 따라 지역 또는 비지역이 될 수 있음 | |
2. 참조환경
참조환경(referencing environment)은 프로그램의 한 위치에서 사용할 수 있는 모든 식별자의 모음이다. 현재 위치의 모든 지역변수와 영역 규칙에 의해 접근 가능한 모든 비지역변수를 포함한다. 참조환경은 단순히 현재 블록의 선언 목록이 아니라, 그 위치에서 실제로 이름을 참조했을 때 연결될 수 있는 선언들의 전체 집합이다.
중첩된 A, B, C 블록에 각각 a, b, c가 선언되어 있다고 하자. A의 위치에서는 지역변수 a가 참조환경을 이룬다. B에서는 지역변수 b와 비지역변수 a를 사용할 수 있다. 가장 안쪽 C에서는 지역변수 c와 비지역변수 a, b가 참조환경에 포함된다.
제3장 영역 규칙과 자유변수
1. 영역 규칙의 역할
영역 규칙(scope rule)은 변수를 참조할 때 그 이름이 어느 선언을 가리키는지 결정하는 방법이다. 특히 현재 블록에서 선언되지 않은 이름, 즉 자유변수(free variable)의 참조 위치를 찾는 데 핵심적인 역할을 한다.
자유변수의 이름과 일치하는 선언을 영역 규칙에 따라 바깥에서 찾으면 그 변수는 현재 블록에서 비지역변수가 된다. 허용된 탐색 범위를 끝까지 조사했는데도 선언을 찾지 못하면 오류가 발생한다. 대표적인 영역 규칙에는 프로그램의 문맥적 포함 관계를 이용하는 정적 영역 규칙과 실행 중 서브프로그램의 호출 관계를 이용하는 동적 영역 규칙이 있다.
2. 같은 코드, 다른 참조 가능성
Pascal 예에서 주프로그램 Main은 변수 A를 선언하고, Sub1은 자신의 선언 없이 A를 사용한다. 별도의 Sub2도 A를 선언한 뒤 Sub1을 호출한다. Sub1의 A가 Main의 변수인지, 호출자인 Sub2의 변수인지, 또는 오류인지는 채택한 영역 규칙에 따라 달라진다.
제4장 정적 영역 규칙
1. 정적 내포 관계
정적 영역 규칙(static scope rule)은 블록들의 정적 내포 관계, 즉 소스 코드 문맥에서 어떤 블록이 어떤 블록 안에 포함되어 있는지를 이용해 변수의 참조 위치를 결정한다. 프로그램을 실행하지 않고도 구조를 판단할 수 있기 때문에 “정적”이라고 하며, 어휘적 영역 규칙(lexical scope rule)이라고도 한다.
현재 블록을 문맥적으로 포함하는 모든 블록을 정적 조상(static ancestors)이라 하고, 그중 현재 블록을 가장 가까이에서 직접 포함하는 블록을 정적 부모(static parent)라고 한다. A 안에 B가 있고 B 안에 C가 있다면 C의 정적 부모는 B이며, C의 정적 조상은 B와 A이다.
2. 적용 절차
정적 영역 규칙에서는 먼저 사용하려는 이름의 선언이 현재 블록에 있는지 찾는다. 있으면 지역변수이다. 없다면 자유변수이므로 정적 부모로 이동해 같은 이름의 선언을 찾는다. 찾으면 현재 블록의 비지역변수로 결정하고, 없으면 그 부모의 정적 부모로 계속 올라간다. 최외곽 영역까지 찾지 못하면 오류이다.
- 현재 블록의 선언을 찾는다.
- 없으면 정적 부모로 이동해 반복한다.
- 가장 먼저 발견한 선언을 비지역변수의 참조 위치로 정한다.
- 최외곽까지 없으면 오류로 판정한다.
3. 정적 영역 규칙의 적용 예
Main 안에 Sub1과 Sub2가 나란히 선언되고, Sub2가 자신의 A를 선언한 뒤 Sub1을 호출하는 예를 보자. Sub1의 정적 부모는 Main이다. Sub2는 Sub1의 정적 부모나 정적 조상이 아니라 문맥상 형제 블록이다. 따라서 Sub1의 자유변수 A는 호출자인 Sub2의 변수가 아니라 Main에 선언된 A를 가리킨다.
Main 안에 Sub1, Sub1 안에 Sub2가 중첩된 경우 Sub2의 자유변수 A를 찾을 때는 먼저 Sub1을 검사하고, 없으면 Main을 검사한다. Sub1에 A가 있으면 그것을 사용한다. Sub1에 없고 Main에 있으면 Main의 변수를 사용한다. 두 곳 모두 없으면 오류가 된다.
4. 영역 구멍
영역 구멍(scope hole)은 원래 보이던 비지역변수가 더 가까운 블록에 선언된 같은 이름의 지역변수 때문에 보이지 않게 되는 영역이다. Main, Sub1, Sub2가 차례로 중첩되고 세 블록 모두 A를 선언하면, Sub1에서는 Main의 A가 가려지고 Sub2에서는 Main과 Sub1의 A가 모두 가려진다.
각 선언에 10, 20, 30을 대입했다고 하면 Main 위치의 A는 10, Sub1 위치의 A는 20, Sub2 위치의 A는 30을 나타낸다. 이름이 같을 때 정적 탐색 과정에서 가장 먼저 만나는 가까운 선언이 선택되기 때문이다.
제5장 동적 영역 규칙
1. 동적 내포 관계
동적 영역 규칙(dynamic scope rule)은 블록들의 동적 내포 관계, 즉 서브프로그램이 실제로 서로를 호출한 관계를 이용해 변수의 참조 위치를 결정한다. 호출 관계는 입력과 실행 경로에 따라 달라질 수 있으므로 프로그램 수행 시점에만 정확히 판단할 수 있다.
현재 블록에 선언이 없으면 그 블록을 호출한 블록으로 이동해 같은 이름의 선언을 찾는다. 없으면 다시 그 호출자를 호출한 블록으로 거슬러 올라간다. 가장 먼저 발견한 선언이 비지역변수의 참조 위치가 되며, 최초 호출자의 영역까지 찾지 못하면 오류가 된다.
2. 호출 관계를 따른 적용
Main이 Sub2를 호출하고 Sub2가 Sub1을 호출하며, Sub1에서 자유변수 A를 사용하는 예를 보자. 동적 탐색 순서는 Sub1에서 Sub2, 그리고 Main이다. Sub2에 A가 선언되어 있다면 Sub1의 자유변수는 Sub2의 A를 참조한다. 이는 동일한 소스 구조에서 Main의 A를 선택했던 정적 영역 규칙과 다른 결과이다.
Main이 Sub1을 호출하고 Sub1이 Sub2를 호출하는 중첩 예에서도 Sub2에 선언이 없으면 호출자인 Sub1을 먼저 확인하고, 이어 Main을 확인한다. 이 특정 호출 순서가 문맥적 중첩 순서와 같아 보일 수 있지만, 동적 규칙의 근거는 소스 코드의 포함 관계가 아니라 실제 호출 기록이라는 점을 구분해야 한다.
3. 정적 규칙과 동적 규칙 비교
| 구분 | 정적 영역 규칙 | 동적 영역 규칙 |
|---|---|---|
| 탐색 관계 | 블록의 문맥적 포함 관계 | 서브프로그램의 실제 호출 관계 |
| 결정 시점 | 컴파일 시점 | 프로그램 수행 시점 |
| 타입 검사 | 정적 타입 검사 가능 | 정적 타입 검사 불가능 |
| 수행 속도 | 빠름 | 느림 |
| 적용 언어 | Algol 60, Pascal, C, C++, Java, Python 등 대부분 | LISP 등 인터프리터 방식의 구현 언어 |
제6장 전역변수와 이름공간
1. 전역변수
전역변수(global variable)는 어떤 블록에도 포함되지 않는 곳에서 선언된 변수이다. 영역은 프로그램 전체이며 각 블록에서는 비지역변수로 취급된다. 여러 블록에서 공유하기 쉽지만, 블록 안에 전역변수와 같은 이름의 지역변수를 선언하면 지역변수가 전역변수를 가려 영역 구멍이 발생한다.
2. C++ 영역 연산자
C++에서는 영역 연산자 또는 영역 결정 연산자(scope resolution operator) ::를 이용해 가려진 전역변수를 명시적으로 참조할 수 있다. 전역에 int i=0;이 있고 main 안에 int i=5;가 있을 때 단순한 i++는 지역변수를 6으로 만든다. ::i++는 전역변수를 1로 만든다. 따라서 지역 i의 출력은 6이고 전역 ::i의 출력은 1이다.
::변수명에서 이름 앞의 빈 영역은 전역 영역을 뜻한다. 같은 이름의 지역변수가 있더라도 전역 선언을 직접 지정하므로 영역 구멍을 피할 수 있다.3. 이름공간의 정의
이름공간(namespace)은 관련성이 높은 변수와 함수를 하나의 묶음으로 관리하는 영역이며, 영역 자체가 이름을 가진다. 예를 들어 C++에서 namespace S1 안에 변수 k와 함수 Output을 정의하면 두 이름은 S1이라는 영역에 속한다. 서로 다른 이름공간에서 같은 식별자를 사용해도 소속 영역을 구분할 수 있으므로 이름 충돌을 관리하는 데 유용하다.
4. 이름공간 밖에서 사용하는 두 방법
첫째, 이름공간 이름과 영역 연산자를 붙여 S1::k, S1::Output()처럼 직접 지정할 수 있다. 이 방식은 어떤 이름공간의 구성원인지 참조 지점에서 명확하게 드러난다. 둘째, using namespace S1;을 사용하여 S1의 이름들을 현재 범위에서 직접 사용할 수 있게 할 수 있다. 그러면 k, Output()처럼 이름공간 접두어 없이 쓸 수 있다.
강의 예에서 처음 S1::k++를 실행하면 이름공간 변수는 1이 되고 S1::Output()은 k=1을 출력한다. 이어 using namespace S1; 뒤에 k++와 Output()을 실행하면 같은 이름공간 변수는 2가 되어 k=2를 출력한다.
5. 이름공간과 영역 구멍
using namespace S1;을 사용한 범위에 같은 이름의 지역변수 k가 있으면 단순한 k는 지역변수를 가리킨다. 그러나 S1::k라고 한정하면 가려진 이름공간 변수를 정확히 지정할 수 있다. 강의 예에서 지역 k는 5에서 k++를 거쳐 6이 되고, S1의 k는 S1::k++로 0에서 1이 된다. Output()은 S1의 변수를 출력하므로 결과는 k=1이다.
using은 이름공간의 이름을 현재 범위에서 쉽게 사용하게 하지만, 같은 이름의 지역변수가 있으면 가려질 수 있다. 이때 S1::이름처럼 영역을 명시하면 원하는 구성원을 선택할 수 있다.핵심 개념 정리
영역은 프로그램에서 변수를 사용할 수 있는 위치의 범위이고 수명은 실행 중 변수가 존재하는 시간이다. 블록은 영역을 구분하는 단위이며, 지역변수는 현재 블록에서 선언된 변수, 비지역변수는 현재 위치에서 사용할 수 있지만 현재 블록 밖에 선언된 변수이다. 한 위치에서 보이는 모든 지역·비지역 식별자의 모음이 참조환경이다.
영역 규칙은 자유변수의 선언 위치를 찾는다. 정적 영역 규칙은 소스 코드의 문맥적 포함 관계를 따라 정적 부모와 조상을 탐색하며 컴파일 시점에 결정된다. 동적 영역 규칙은 실행 중 호출 관계를 따라 호출자를 탐색하며 수행 시점에 결정된다. 더 가까운 같은 이름의 선언이 바깥 선언을 가리는 범위가 영역 구멍이다.
전역변수는 어떤 블록에도 포함되지 않는 곳에 선언되어 프로그램 전체를 영역으로 갖는다. C++의 ::는 가려진 전역변수나 특정 이름공간의 구성원을 명시한다. 이름공간은 관련 변수와 함수를 이름 있는 영역으로 묶으며, S1::이름으로 직접 한정하거나 using namespace S1으로 현재 범위에서 이름을 사용할 수 있다.
예상문제 20선
1. 변수의 영역(scope)을 가장 정확하게 설명한 것은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ③
영역은 소스 프로그램의 어느 위치에서 특정 변수 이름을 사용할 수 있는지를 나타낸다.
2. 영역과 수명의 비교로 옳은 것은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ①
영역은 어디에서 참조할 수 있는지, 수명은 실행 중 언제 변수가 존재하는지를 설명한다.
3. 블록에 대한 설명으로 옳지 않은 것은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ④
Pascal에서는 begin-end 복합문 자체가 블록이 아니며 주프로그램과 서브프로그램이 블록이 된다.
4. 어떤 블록 안에서 직접 선언되어 사용되는 변수는?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ②
현재 블록에서 선언된 변수는 그 블록의 지역변수이다.
5. 중첩 블록 C에서 지역변수 c와 바깥 블록의 변수 a, b를 모두 사용할 수 있다. C 위치의 참조환경은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ②
참조환경은 해당 위치에서 사용할 수 있는 모든 지역변수와 비지역변수의 모음이다.
6. 자유변수에 대한 설명으로 가장 적절한 것은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ④
자유변수는 현재 블록에 선언이 없으며, 영역 규칙을 따라 선언을 찾으면 비지역변수가 되고 끝까지 없으면 오류가 된다.
7. 정적 영역 규칙이 변수 선언을 찾을 때 이용하는 관계는?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ①
정적 영역 규칙은 소스 코드에서 블록들이 어떻게 중첩되었는지를 이용하며 프로그램 실행 없이 판단할 수 있다.
8. 현재 블록을 문맥적으로 포함하는 블록 중 가장 가까운 블록을 무엇이라 하는가?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ③
정적 부모는 현재 블록에 가장 가까운 정적 조상이다.
9. Main 안에 Sub1과 Sub2가 나란히 선언되고, Sub2가 자신의 A를 선언한 뒤 Sub1을 호출한다. 정적 영역 규칙에서 Sub1의 자유변수 A는 무엇을 참조하는가?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ④
Sub1의 정적 부모는 Main이다. Sub2는 문맥상 형제 블록이므로 Sub2의 선언은 정적 탐색 경로에 없다.
10. 영역 구멍(scope hole)의 정의로 옳은 것은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ①
가까운 블록의 같은 이름 선언이 바깥 변수의 이름을 가리면 그 바깥 변수의 영역에 구멍이 생긴다.
11. Main, Sub1, Sub2가 차례로 중첩되고 각각 A에 10, 20, 30을 저장했다. 정적 영역 규칙에서 Sub2 내부의 단순한 A가 나타내는 값은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ③
Sub2의 지역변수 A가 가장 가까운 선언이므로 Main과 Sub1의 같은 이름 변수는 가려진다.
12. 동적 영역 규칙이 자유변수의 선언을 찾는 순서는?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ②
동적 영역 규칙은 실행 중의 호출 사슬을 따라 가장 가까운 선언을 찾는다.
13. Main이 Sub2를 호출하고 Sub2가 Sub1을 호출하며 Sub2에 A가 선언되어 있다. 동적 영역 규칙에서 Sub1의 자유변수 A는?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ①
Sub1의 직접 호출자는 Sub2이므로 동적 탐색에서 Sub2의 선언을 먼저 발견한다.
14. 정적 영역 규칙과 비교한 동적 영역 규칙의 특징은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ④
동적 영역 규칙은 실제 호출 관계를 수행 중에 확인하므로 정적 검사에 불리하고 탐색 비용 때문에 느리다.
15. 어떤 블록에도 포함되지 않는 곳에서 선언되고 프로그램 전체를 영역으로 갖는 변수는?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ②
전역변수는 특정 블록 안이 아닌 전역 영역에 선언되며 모든 블록에서 비지역변수로 취급된다.
16. C++에서 지역변수와 같은 이름 때문에 가려진 전역변수 i를 직접 지정하는 표현은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ③
이름 앞에 영역 연산자만 붙인 ::i는 전역 영역의 i를 뜻한다.
17. C++에서 이름공간(namespace)의 역할로 옳은 것은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ③
이름공간은 연관된 이름을 이름 있는 영역으로 관리하여 소속을 구분하고 이름 충돌을 줄인다.
18. 이름공간 S1의 변수 k를 이름공간 밖에서 명시적으로 사용하는 표현은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ②
이름공간 이름, 영역 연산자, 구성원 이름을 차례로 쓴 S1::k가 정확한 한정 이름이다.
19. using namespace S1;의 효과로 가장 적절한 것은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ④
using 지시를 사용하면 S1의 이름을 현재 범위에서 직접 사용할 수 있지만 같은 이름의 지역 선언에 의해 가려질 수 있다.
20. S1의 k는 0, main의 지역 k는 5이다. using namespace S1; 뒤에 k++;, S1::k++;, Output();을 차례로 실행한다. 옳은 결과는?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ①
단순 k는 더 가까운 지역변수를 6으로 만들고, 한정된 S1::k는 이름공간 변수를 1로 만든다. Output은 S1에 정의된 k를 출력한다.
댓글
댓글 쓰기