8장 영역과 수명
8.1 블록과 영역
영역 (Scope)
- 식별자의 효력을 나타낼 수 있는 공간
- 식별자의 사용이 허락되는 프로그램의 범위
* 식별자 (Identifier)
- 변수, 상수, 레이블, 데이터 형, 서브프로그램 등의 이름
수명 (Extent, Lifetime)
- 식별자(변수)값을 보유할 메모리가 배정되어 있는 실행시간
- 식별자의 메모리 할당부터 해제까지의 시간
식별자 영역 제공의 간결한 방법
- 모든 식별자를 전 프로그램에서 사용가능하게 하는 것 (전역 변수화)
- 모든 프로그램에서 모든 식별자를 공유하게 하는 방법이다.
문제점
- 식별자 사용의 복잡화
- 식별자 혼돈
- 프로그램 합성 시 문제점 발생
해결책
- 단위 프로그램의 식별자 사용 (지역화 요구)
ex) Algol 60에서는 begin-end 구조를 사용하여 블록 입구에서 메모리를 할당하고, 블록 출구에서 메모리를 해제한다.
- 블록끼리는 nested, disjoint 구조를 허용한다.
- 복합문(Compound Statement)와 대조적이다.
8.2 정적 영역과 동적 영역
정적 영역 규칙 (Static Scope Rule)
- 식별자의 사용 영역을 블록의 정적 내포관계로 결정하는 규칙
- 즉, 컴파일러가 식별자의 사용 영역을 결정
1) 지역 변수 (Local Variable) - 현재 블록에서 선언된 변수, 함수의 형식 매개변수
2) 비지역 변수 (Non-Local Variable) - 현재 블록에서 사용되나 바깥 블록에서 선언된 변수
3) 전역 변수 (Global Variable) - 모든 블록에서 사용 가능한 변수
* 자유변수 (Free Variable): 현재 블록에서 선언되지 않고, 사용되는 변수 (지역 변수 + 비지역 변수 개념)
* 영역 구멍 (Hole-in-Scope): 내포된 블록 사이에 동일 지역 변수를 선언하면,
바깥 블록의 지역 변수는 내부 블록 구간에서 사용할 수 없게 되는 현상
- Block:b에 속해있는 변수 i, x는 Block:a의 Non-Local Variable이다.
- Block:d에 속해있는 변수 x, y는 Block:c의 Non-Local Variable이다.
- Block:c 혹은 Block:d에서 Block:a에서 선언된 변수 x, y를 사용하지 못하는 현상을 영역 구멍 (Hole-in-Scope)이라고 한다.
정적 영역 규칙을 따른 변칙 현상 (Anomaly)
- 영역 구멍 (Hole-in-Scope)
- 영역과 선언의 가시성(Visibility)에 약간의 차이를 가짐
ex) Ada, Java에서는 영역 한정자에 의해 접근이 가능하다.
Block:c에 속해있는 상황에서 \(\texttt{a.x}\)로 표현하여 Block:a의 변수에 접근할 수 있다.
동적 영역 규칙 (Dynamic Scope Rule)
- 식별자의 영역이 실행시간에 확정
- 식별자의 사용 영역이 컴파일러가 아닌, 프로그램의 실행 순서에 의해 결정
- 인터프리터 언어에서 주로 사용
ex) APL에서의 동적 영역 규칙
- MAIN(주 프로그램)이 SUB를 호출하고, SUB는 FUN을 호출하며, MAIN 또한 FUN을 호출하는 구조이다.
- FUN의 입장에서는 호출하는 주체(MAIN, SUB)에 따라 Scope가 달라진다.
- 최종적으로, Block: scope에서 "a := p; q;" 가 실행되었을 때,
정적 영역 규칙의 결과를 적용하면(단순히, 함수 간 내포 관계에 의해 변수의 영역이 결정) print에서 a와 b는 scope의 변수, p는 함수 p의 리턴값을 의미하며,
동적 영역 규칙의 결과를 적용하면(프로그램의 실행 순서에 변수의 영역이 결정) print에서 a는 scope의 변수, b와 p는 q의 변수를 의미하게 된다.
- PL/I에서는 위와 같은 변수 선언의 혼동 문제가 존재한다.
- D에서는 C에서의 J=Y가 C에서의 독립적인 선언인지, 값 배정인지에 관한 혼동이 초래된다.
8.3 언어에서의 영역
Algol 68
- begin/end 블록 개념이 일반화(세분화)되었다. (문맥 시작과 마침 기호가 다양하다.)
ex) if-fi, then-else, else-fi, begin-end, loop-pool, case-esac 등
- 영역 블록: 모든 문맥 시작 기호와 문맥 마침 기호 사이를 의미
- 선언: 블록 입구에서 선언하며, 블록 끝에서 해제된다. (선언의 영역은 블록 단위이다.)
Pascal
- begin/end 블록 개념이 있으나, 복합문 개념이며 영역 블록 개념이 아니다.
- Pascal의 영역 단위는 프로시저/함수 이다.
- 정적 영역 규칙을 따르며, 프로시저를 내포할 수 있다.
C/C++, Java
- Algol과 같은 블록 개념을 따른다.
- begin/end 구조 대신, 중괄호를 사용하여 영역을 정의한다.
* C++, Java
- C++, Java 두 언어 모두 어디에서나 변수 정의를 할 수 있다. (변수의 Scope: 정의문의 블록의 끝)
- for문의 초기화식에 제어 변수 정의를 허용한다. (제어 변수의 Scope: 표준에서는 for문, 초기버전에서는 정의문의 블록 끝)
- C/C++, Java에서는 Hole-in-Scope 문제를 오른쪽과 같이 변수명을 바꿔서 해결한다.
- p에서 선언한 변수 x는 내부 블록에서 Hole-in-Scope 상태에 빠진 예시이다.
- C에서는 모든 함수 전체를 포함하는 외부 영역(External Scope)가 존재한다.
- 또한, 외부 영역만이 전역이다.
블록 구조를 통한 영역 개념의 장점
1. 지역성(Locality) 제고
- 변수를 사용할 프로그램 가까이서 선언하여 가독성을 높이고, Cache 효율성을 개선한다.
2. OS하에서 작은 Working Set 요구
- Cache 효율이 높아, OS에서의 전체적인 Working Set의 크기가 작아진다.
* Working Set: OS가 즉흥적으로 사용하는 메모리를 의미한다.
3. 표준 패키지를 사용자 프로그램에 결합시켜 하나의 프로그램을 만들기 쉽다.
- 블록 단위로 선언된 변수들끼리는 간섭이 적기 때문이다.
4. 프로그램의 구성을 Stepwise Refinement(단계적 세분화)하는데 도움이 된다.
- 프로그램의 조립이 가능해진다. (모듈화)
8.4 변수의 수명 (Extent, Lifetime)
- 변수가 메모리를 할당받는 기간
Algol 60
- 블록 단위로 메모리가 할당/해제된다.
- Algol 60 변수의 Extent: 선언된 블록 내에서의 블록 시작 ~ 블록 종료
※ own 변수
- Algol 60에서의 static 변수로, 프로그램(메인 프로시저) 종료 시까지 계속 유지되는 변수를 의미한다.
- 프로그램 종료시까지 유지되지만, own 변수의 Scope는 선언된 블록 내부이다.
- 초기화는 최초 진입 시 한 번만 수행된다.
PL/I
- automatic 변수의 Extent: 블록 진입 ~ 블록 탈출
- static 변수의 Extent: 메인 프로시저 진입 ~ 메인 프로시저 탈출
- controlled(heap 개념) 변수의 Extent: allocate() 실행 ~ free() 실행
- based(stack 개념) 변수의 Extent: allocate() 실행 ~ free() 실행
메모리 데이터 형 | 메모리 할당 시점 | 메모리 소멸 시점 |
STATIC | 메인 프로시저의 시작 지점 | 메인 프로시저의 종료 시점 |
AUTOMATIC | proc/block의 시작 지점 | proc/block의 종료 시점 |
CONTROLLED | ALLOCATE 실행 시점 | FREE문 실행 시점 또는 Task를 벗어나는 시점 |
BASED | ALLOCATE 실행 시점 | FREE문 실행 시점 또는 Task를 벗어나는 시점 |
동적 수명
- heap 기법을 사용하여 Extent를 동적으로 관리하는 개념이다.
ex) Pascal: new() ~ dispose()
ex) PL/I: ALLOCATE() ~ FREE()
ex) C: malloc() ~ free()
ex) C++: new ~ delete
8.5 Ada의 영역
- Ada의 영역 단위: 서브프로그램(procedure, fuction), 패키지(package), 태스크(task)
※ 영역 단위 구조
procedure, function | package | task |
procedure NAME(parameter) is 선언부 begin 문장부 end; |
package NAME is 명세부 end; package body NAME is 몸체 end; |
task NAME is 명세부 end; task body NAME is 몸체 end; |
선언부와 문장부가 붙어있음 | 명세부와 몸체가 분리되어 있음 | 명세부와 몸체가 분리되어 있음 |
- 명세부들이 분리되어 있음으로, 명세부끼리 모아서 변수의 Compatibility를 미리 검사할 수 있게 된다.
- Ada에서는 Scope Qualifier(".")를 이용하여 Hole-in-Scope를 극복한다.
- Ada에서는 Scope Qualifier(".")를 이용하여 Hole-in-Scope를 극복한다.