프로그래밍 언어론 4장 - 변수 및 유효범위

프로그래밍 언어론 4장 - 변수 및 유효범위

1. 변수 선언

선언 대상 : 상수, 변수, 함수 등

선언 위치 : 블록 내 혹은 밖에 변수 선언, 구조체 내 필드 변수 선언, 클래스 내에 필드 변수 혹은 함수 선언

선언 방법 :

- 명시적 선언 언어 : Pascal, C, C++, Java 등

- 묵시적 선언 언어 : Lisp, Python, ML 등

정적 타입 언어

- Declaration before use

대부분의 언어에서 변수는 사용 전에 먼저 선언해야 한다. (어떤 언어에서는 선언하지 않아도 사용 가능하다.)

보통 정적 타입 언어(statically typed language)에서 적용된다.

정적 타입 언어(static typed language) 예시 : C, C++, JAVA

- 변수의 유효범위(scope)

선언된 변수가 유효한(사용될 수 있는) 프로그램 내의 범위, 영역이다. 변수 이름 뿐 아니라 함수 등 다른 이름도 생각해야 한다.

- 정적 유효범위(Static scope) 규칙

선언된 이름은 선언된 블록 내에서만 유효하다. 대부분 언어에서 표준 규칙으로 사용된다.

- 정적 타입 언어의 구문법

stmt -> ... | id = ; | let in end -> { id [=];} -> {} -> int | bool | string

변수 id는 타입 변수이며 초기화가 가능하다.

초기화하지 않은 변수는 자동으로 기본값으로 초기화한다.

변수 id는 지역변수로 유효범위는 선언된 블록 내이다.

또한 블록은 중첩될 수 있으며 let 블록 내의 문장에 다시 let 블록이 나타날 수 있다.

let D1; in let D2; in end; end;

블록이 중첩되면 꽤 복잡해진다. 그래서 다음과 같이 간단한 형태로 선언할 수 있게 되어 있는데 일종의 설탕구문(Syntactic sugar, =문법적 설탕, 사람이 이해하고 표현하기 쉽게 디자인된 프로그래밍 언어 문법)으로 볼 수 있다.

let D1;D2; in end; ex) let int x; int y; in x = 1; y = x + 1; end;

같은 이름의 변수가 여러 개 선언되었을 때 유효범위는 다음의 예시를 들어 확인해 볼 수 있다.

let int x = 1; in x = 1 let int y = 0; in x= 1, y = 0 (지역 변수) y = x + 2; x = 1, y = 3 end; x = 1 let int x = 5; in x= 5 ( 지역 변수) x = x + 1; x = 6 end; x = 1 (바깥 블록의 변수는 남아있다) x = x * 2; x = 2 end;

동적 타입 언어

변수의 타입을 선언하지 않고 바로 사용한다.

변수에 어떤 타입의 값이든지 대입, 저장할 수 있다.

동적 타입 언어(Dynamically typed Language) 예시 : Lisp/Scheme, Javascript, Python 등

python의 예)

대입문을 사용하여 변수에 대입하면 변수는 자동으로 생성된다.

id =

>>> score = 80 >>> print(score) 80 >>> score = "80%" >>> print(score) 80%

전역 변수를 사용하는 것도 가능한데, 한 가지 주의해야 할 점이 함수 내에서 전역 변수의 값을 수정할 수 없다는 점이다. 함수 내에서 대입문을 이용해 전역 변수 값을 수정하려고 시도하면 Python 은 자동적으로 그 이름에 해당하는 새로운 지역 변수를 만들어 내기 때문이다.

함수 내에서 전역 변수를 사용하는 방법은 전역 변수를 함수 내에서 global로 선언하는 것이다. 다음의 예시와 같다.

아래 예시에서는 20을 값으로 가진 전역 변수 percent가 함수 안에서 30으로 수정되는 것을 볼 수 있다.

>>> percent = 20 >>> def salePrice(price): global percent percent = percent + 10 result = price * (1 - percent/100) return result

2. 블록 구조 언어

블록

서로 연관된 선언문과 실행문들을 묶어 놓은 프로그래밍 단위이다.

블록은 변수나 함수를 선언하는 선언문들과 실행문들로 구성된다.

[블록을 나타내는 기호]

- 중괄호({}) : C

- begin-end : Ada

- 프로시저 또는 함수 : C, Pascal 등

- let... in... end : ML 등

[블록의 중첩을 허용하는 언어] = 블록 구조 언어(Block structured language)

Algol, Pascal, Modula, Ada, C, ...

정적 유효범위 규칙에 따라 블록에서 선언된 변수는 선언된 블록 내에서만 유효하다.

블록 구조 언어의 특징 및 장점

(1) 대형 프로그램을 여러 블록으로 나눠 작성하면 복잡한 수행 내용을 단순화하며 프로그램의 해독성을 높여준다.

(2) 프로그램 오류 발생시에도 범위가 블록 단위로 한정되므로 수정이 쉬워지며 블록의 첨가, 삭제, 수정 등이 용이하다.

(3) 블록 내에 선언된 변수들은 그 안에서만 유효하며 실행이 종료되면 기존에 선언되었던 변수들은 모두 무효화된다.

(4) 사용자로 하여금 변수의 사용과 기억 장소의 할당에 관한 경계를 명확하게 할 수 있다.

C 언어의 유효범위 규칙

- 핵심 아이디어 : 사용 전 선언(Declaration before use). 선언의 유효범위는 선언된 지점부터 선언된 블록 끝까지로 한다.

최중첩 우선 규칙이 적용된다. (가장 가까운 것을 쓴다. 전역 변수가 있더라도 지역 변수가 있다면 지역 변수를 사용한다.)

int x = 1; // 전역 변수 선언 void p() // 함수 p 정의 { char x; // 지역 변수 x 선언 x = 'A'; // 지역 변수 x 사용 } void q() // 함수 q 정의 { double y = 2; // 지역 변수 y 선언 { int z = 3; // 중접 블록에서 지역 변수 z 선언 x = y + z; // 전역 변수 x 사용 } } int main() // main 함수 정의 { int w[10]; // 지역 변수 선언 x = x + 1; // 전역 변수 사용 }

Ada 언어의 유효범위 규칙

- 핵심 아이디어 : 선언된 유효범위는 선언된 블록 내

B1 안에서 x,y가 유효하고, B2 안에서 a,b가 유효하다.

B1: declare // B1 시작 x:integer; y:boolean; begin x := 2; y := false; B2: declare // B2 시작 a, b: integer; begin if y then a := x; else b := x; end if end B2; // B2 끝 end B1; // B1 끝

ML 블록

- ML 블록 구조

let 변수 선언 함수 선언 in 실행문 end; ---------------------------------- let val x = 1 // 전역 변수 fun f(x:int) = x + x // 매개변수 in f(x+1) end;

Java의 구조

블록 구조 언어가 아님.

private, protected, public 으로 변수, 메소드, 클래스의 유효범위를 결정한다.

public class IntWithGcd // 전역 클래스 { private int value; // 지역 변수 (클래스 내에서 사용한다) public int getValue() { // 전역 메서드 return value; } public int gcd(int v) { // 전역 메서드 int tmp, n; // 지역 변수 ... } }

3. 변수의 상태와 의미

변수

메모리 위치(주소)를 나타내는 이름이다.

대입문의 예)

x = x + 1;

왼쪽 변수 x는 메모리 위치(l-value)를 나타내고 오른쪽 변수 x는 메모리에 저장된 값(r-value)를 나타낸다.

따라서 만약 x의 값이 2라면 x의 위치에는 2 + 1의 결과값인 3이 저장된다.

변수는 프로그램 안에서 가변적인 값을 가진다. 이 변수들의 그때 그때의 값들, 현재 값을 상태(state)라고 한다.

상태 s는 다음과 같이 변수 이름 집합인 Identifier에서 값 집합인 Value로 가는 하나의 함수로 정의할 수 있다.

변수 x, y, z의 현재 값이 각각 1, 2, 3일 때, 상태는 다음과 같이 정의할 수 있다.

s:Identifier -> Value s = {x↦1, y↦2, z↦3}

상태는 하나의 함수로 생각할 수 있으며 상태 s에서의 변수 값은 s(x)로 표현할 수 있다.

상태 갱신 기호는 s' = s[y↦v] 로 나타낼 수 있다.

수식의 의미

- 수식 E : 상태에서 수식의 값을 말한다.

V : (State, Expr) → Value

예)

s = {x↦1, y↦2}

V(s, x+y) = 3

E → true | false | n | str | id

V(s, true) = T

V(s, false) = F

V(s, n) = n (상수)

V(s, str) = str

V(s, id) = s(id) - 산술 수식

E → E + E | E - E | E * E | E / E

V(s, E1 + E2) = V(s, E1) + V(s, E2)

s = {x↦1, y↦2}

V(s, x + y) = V(s, x) + V(s, y) = s(x) + s(y) = 1 + 2 = 3 - 비교 수식

E → E > E | E < E | E == E | E != E

V(s, E1 == E2) = T if V(s, E1) == V(s, E2)

F otherwise

s = {x↦1, y↦2}

V(s, x > y) = V(s, x) > V(s, y) = s(x) > s(y) = 1 > 2 = F

문장의 의미

- 문장 S : 문장 S가 전 상태 s를 후 상태 s'로 변경시킨다.(=상태 변환, 상태 전이)

상태 변환 함수(state transform function)는 Eaval로 정의할 수 있다.

Eval : (State, Statement) → State

Eval(s, S) = s' for each statement S

각 문장 S마다 상태 변환 함수를 정의한다.

프로그램의 실행과정을 상태 변환 과정으로 설명한다.

대입문 id = E

Eval(s, id = E) = s[id ↦ V(s, E)]

복합문 S; S

Eval(s, S1;S2) = Eval(Eval(s, S1), S2)

예시)

let int x; in x = 1; (1) let int y; in y = 2; (2) x = x + y; (3) end; end;

(1) s = {x↦0}

Eval(s, x=1) = s[x↦1] = {x↦1}

(2) s = {x↦1, y↦0}

Eval(s, y=2) = s[y↦2] = {x↦1, y↦2}

(3) s = {x↦1, y↦2}

Eval(s, x=x+y) = s[x↦V(s, x+y)] = s[x↦3] = {x↦3, y↦2}

4. 변수의 유효범위 관리

유효한 변수들의 상태(값) 정보를 유지하는 방법에 대한 기본 아이디어는 다음과 같다.

- 블록 시작을 만났을 때 : 선언된 변수의 값을 나타내는 상태 정보를 새로 생성해 유지한다.

- 블록 내 문장을 만났을 때 : 유지되고 있는 유효한 변수들의 상태 정보를 이용해서 문장들을 해석한다.

- 블록 끝을 만났을 때 : 선언된 변수들은 더 이상 유효하지 않으므로 그들의 상태 정보를 제거한다.

상태(State)를 Stack 형태로 변수를 유지 및 관리할 수 있다.

변수는 Stack의 First-In Last-Out / Last-In First-Out 형태로 유지, 관리된다.

따라서 전역 변수와 지역변수가 있을 때, 전역변수가 지역 변수 때문에 가려져서 보이지 않을 수가 있다.(전역 변수가 먼저 스택에 들어왔기 때문에 그 위에 지역 변수가 들어오고 가려진다.)

이때 전역 변수가 보이지 않는 부분에서 영역 구멍(hole-in-scope)를 가진다고 한다.

[출처] 프로그래밍 언어론 - 창병모

from http://soso-study.tistory.com/48 by ccl(S) rewrite - 2021-10-18 04:28:20