Sign In

[프로그래밍 언어] 2. Names, Bindings, and Scopes

Y
Yerim
  1. Programming Language

이름 Names & 5.3 변수 Variables

변수 Variables

•
컴퓨터 메모리 셀이나 셀들의 모임에 대한 추상화
•
프로그래머들은 흔히, 변수를 메모리 위치들에 대한 이름으로 생각
•
변수의 6가지 속성: 이름, 주소, 값, 타입, 수명, 영역

이름 Names

•
변수의 기본적인 속성 중 하나
•
식별자 identifier라는 용어와 혼용되어 사용
•
프로그램에서 어떤 개체를 식별하기 위해 사용되는 문자열
→ 문자 중간에 숫자는 괜찮지만 첫 번째로 오는 것은 안된다
이름 형식
•
언어에 따라 이름의 길이 제한이 존재 (Java, Python은 길이 제한 없음)
•
C언어는 31글자까지 구분 가능 (32번째 글자부터는 달라도 같은 인수로 인식)
•
많은 언어들은 대소문자를 구분한다 (구분하지 않는 언어도 있음)
•
표기법
◦
낙타 표기법 Camel Notation
▪
이름이 길어지면 문장을 이름으로 만든다
▪
띄어쓰기를 하면 하나의 변수로 인식 X → 붙여써야 한다.
▪
첫 번쨰 단어를 제외하고 대문자로 시작 → myStack
◦
Snake Case: C언어에서 _로 띄어쓰기 구분
◦
Kebab Notation: -로 띄어쓰기 구분

주소 Address

•
변수와 관련된 기계 메모리 주소 → 연관이 그리 단순하지 않다
◦
변수의 이름이 없고 주소만 있어도 변수에 접근 가능
→ 힙 메모리, malloc을 리턴
◦
변수에는 값을 포함한다 → 초기화되지 않는 경우도 있다
•
많은 언어에서, 동일한 변수가 프로그램의 다른 시기에 다른 주소와 연관 가능
◦
부프로그램의 지역 변수 → 호출될 때마다 스택 메모리에서의 위치에 따라 주소 값이 달라질 수 있다
x = [2, 3, 4]
y = x     # 참조가 복사
y[2] = 15 # [2, 3, 15]?
print(x)  # x = [2, 3, 15]
print(y)  # y = [2, 3, 15]
•
Python에서 변수의 타입은 어떤 값을 넣는가에 따라 바뀐다
i = 0; // 10을 i라는 변수 주소에
j = i; // i의 값을 j의 주소에
•
l-value에는 주소, r-value에는 값이 들어간다
int* a;
a = 100;
*a = 10;
별칭 Alise
•
포인터와 같다
•
동일한 주소를 갖는 여러 개의 변수를 갖는 것이 가능
•
두 개 이상의 변수 이름이 동일한 메모리 위치를 접근하는데 사용할 수 있을 때 별칭
•
변수의 값이 다른 변수에 대한 배정문에 의해 변경되는 것을 허용 → swapping 가능
•
별칭 생성 방법
◦
C언어에서 공용체 타입 사용
◦
C언어에서 두 개의 포인터 변수가 동일한 메모리 위치를 가리키면, 이들은 별칭
◦
Java나 Python에서 참조 변수들도 별칭
•
main 프로그램과 부프로그램(함수 등)이 존재
•
→ 많은 언어에서 별칭은 부프로그램 매개변수를 통하여 생성 가능

타입 Type

char c;
c = 7; // dtype에 따라 해석이 달라진다
•
타입이 결정 하는 것
◦
변수가 사용할 수 있는 범위
◦
그 타입의 값들에 대해서 정의되는 연산들의 집합
•
정수 타입은 덧셈, 뺄셈, 나눗셈, 모듈러 등의 산술 연산 포함 (실수 타입은 X)

값 Value

typedef int INT;    // int: 데이터 타입
typedef struct A x; // struct A: 저장할 수 있는 값
•
변수에 연관된 메모리 셀이나 셀들의 내용
•
변수의 값을 때때로 r-value라고 부름

존속기간 Lifetime

•
언제 만들어서 언제 사라지는가

영역 Scope

•
Closure: 함수 선어 시의 환경을 기억했다가 사용
•
Clojure: 함수형 프로그래밍 언어

바인딩의 개념 The Concept of Binding

바인딩

•
속성과 엔터티 사이의 연관
◦
엔터티 entity: 사람 또는 사물 (객체)
◦
속성 attribute: 엔터티는 속성을 가진다
◦
변수, 함수 등의 엔터티와 속성 사이에 연관 binding이 있다.
C언어 기반 예
int i;  // 1) 컴파일 시점
i = 10; // 2) 실행 시간

// 1) float f
// 2) f = 5
float f = 5;
•
변수와 타입 사이의 연관 → 컴파일 시점에 반인딩
•
변수와 값 사이의 연관 → 실행 시간에 바인딩
•
변수와 주소 사이의 연관 → 메모리 적재 loading 시점에 바인딩 (변수마다 다름)
•
연산과 심볼과의 연관 → 언어 설계 (컴파일 설계) 시점
•
라이브러리 부프로그램 호출과 부프로그램 코드와의 연관 → 링크 시점에 바인딩

변수의 속성 바인딩

정적 바인딩 static binding
•
바인딩이 실행 시간이 시작되지 전에 일어나고
•
프로그램 실행 전체에 걸쳐서 변하지 않은 상태로 남아있다
동적 바인딩 dynamic binding
•
바인딩이 실행 시간 중에 일어나거나
•
프로그램 실행 과정에서 변경될 수 있다

타입 바인딩

•
변수는 프로그램에서 참조되기 전에 어떤 데이터 타입에 바인딩되어야 함
정적 타입 바인딩
•
변수에 바인딩된 타입은 프로그램 단위의 존속기간 lifetime 동안 고정
•
명시적 선언 explicit declaration: 변수 이름들을 나열, 특정 타입이라는 것을 명세
◦
C언어: int i;
•
묵시적 선언 implicit declaration: 디폴트 규칙을 통해 타입 연관
◦
Fortran: I, J, K, L, M, N으로 시작하면 Integer
•
정적 바인딩 언어: C, C++, Java 등
int i = 10;
var j = 10;
•
java에서 var 키워드는 우측 값으로 왼쪽 type을 유추한다
•
컴파일 시간에 int로 바인딩된다
•
한 번 바인딩되면 값이 바뀔 수 없기 때문에 정적이다
동적 타입 바인딩
•
변수의 타입이 선언문으로 명세되지 않으며, 이름의 철자로부터도 결정될 수 없음
•
배정문에서 변수에 어떤 값이 할당될 때, 그 변후에 어떤 타입이 바인딩
•
배정문은 그 변수를 다른 주소, 메모리 셀에 바인딩 가능
→ 다른 타입 값은 프로그램 실행 동안 여러 번 변경될 수 있기 때문
•
동적 타입 바인딩되는 변수의 타입은 일시적일 수 있음
•
동적 타입 바인딩 언어: 파이썬, JavaScript, Ruby, PHP 등
•
장점
spam = 'Hello' # 문자열 타입
spam = 40      # 정수 타입
spam = 1.0     # 실수 타입
◦
포괄적 프로그램 작성 등 프로그래밍에 유연성을 제공한다
◦
포괄적 프로그램: 어떤 타입도 처리할 수 있은 함수
▪
두 개의 숫자를 파라미터로 받아서 더하는 함수를 만든다면
▪
float, double, int 타입에 대해 add를 모두 만들어야 한다
▪
포괄적 프로그램을 사용하면 개발해야하는 메소드들의 수가 줄어든다
◦
데이터가 입력된 후에 그 변수에 할당될 때 올바른 타입으로 바인딩된다
•
단점
◦
프로그램을 덜 신뢰적으로 만든다
▪
코드가 커지면 데이터 타입을 보장하지 못하기 때문에 신뢰도가 낮아진다
▪
따라서 타입 검사를 통해 검증해야한다
◦
비용이 많이 든다
▪
실행 시간에 검사가 수행되어 실행 속도가 느려질 수 있다
▪
모든 변수가 현재의 타입을 유지하고 관리 → 메모리 비용 발생

기억공간 바인딩과 존속 기간

•
명령형 프로그래밍 언어 → 그 변수들에 대한 기억 공간 바인딩 설계로 결정됨
•
할당 allocation과 회수 deallocation
◦
할당: 변수에 바인딩되는 메모리 셀을 가용 메모리의 풀로부터 가져옴
◦
회수 : 변수로부터 바인딩이 해제된 메모리 셀을 다시 가용 메모리 풀에 반환
•
변수의 존속기간 lifetime
◦
변수가 특정 메모리 위치에 바인딩되어 있는 기간
◦
시작 시점: 변수가 특정 셀에 바인딩
◦
종료 시점: 그 변수의 바인딩이 그 셀로부터 해제
•
존속기간에 따른 변수들의 4가지 분류:
정적 변수, 스택-동적 변수, 명시적 힙-동적 변수, 묵시적 힙-동적 변수
정적 변수 static variable
•
프로그램 실행 전 메모리 셀에 바인딩
•
프로그램 실행 종료까지 동일한 메모리 셀에 바인딩
•
전역 변수 → 프로그램 실행 전체에 걸쳐 사용
•
지역적 정적 변수 → 과거 민감 부프로그램에서 사용
◦
class 안에 멤버 변수, 메서드에 static을 붙이면 class 변수, class 메서드
◦
main 함수는 프로그램이 실행 되기 전에 존재해야한다
◦
프로그램이 실행되기 전 메모리에 존재해야하는 메서드에는 static을 붙임
•
장점
◦
효율성: 정적 변수에 대한 모든 주소지정은 직접적
◦
실행 시간에 할당, 회수 등 메모리 관리 비용이 필요 없기 때문에 부담 없음
•
단점
◦
유연성 감소: 재귀적 부프로그램 지원 불가 (재귀는 변수들이 리셋 → 동적)
◦
기억공간이 변수들 간에 공유될 수 없음
스택-동적 변수 static-dynamic variable
•
변수의 선언문이 실제 실행될 때, 기억 공간에 바인딩
•
변수의 타입은 정적으로 바인딩
•
스택 -동적 변수는 실행-시간 스택 run-time stack 에 할당
•
장점: 유연성 증가: 재귀적 부프로그램 사용 가능
•
단점
◦
할당과 회수에 따른 실행-시간 부담
◦
간접 주소지정이 요구되어 접근이 느려짐 → 스택에서 위치가 계속 바뀌기 때문
◦
부프로그램이 과거 민감할 수 없음 → 필요시 static 사용
•
예시
◦
C 함수, Java 메소드 시작 부분에 변수 선언
▪
함수나 메소드가 호출될 때 바인딩
▪
함수나 메소드 실행이 종료될 때 회수
◦
C 함수, Java 메소드 중간 부분에 변수 선언
▪
함수 나 메소드가 호출될 때 바인딩
▪
변수 선언 부분이 실행될 때까지 가시화되지 않음
명시적 힙-동적 변수
•
프로그래머가 명세하는 명시적 실행-시간 명령어에 의해 할당되고 회수되는 이름없는 (추상) 메모리 셀
•
힙heap으로부터 할당되고 회수
•
포인터나 참조 변수를 통해 참조될 수 있다
•
장점: 연결 리스트나 트리 같은 동적 구조체 구현 가능
•
단점
◦
포인터나 참조 변수의 올바른 사용 어려움
◦
요구된 기억 공간 관리 구현의 복잡성
// Java
Integer intnode;
intnode = new Integer(1); // new: 명시적인 동적변수 할당, 1을 초기값으로 사용
// 회수 명령어 없음 -> dangling pointer 문제 해결
// C언어
int *intnode;

// intnode: 전역 vs 지역에 따라 다름 
// (int *)malloc(sizeof(int)): 명시적인 동적 변수
intnode = (int *)malloc(sizeof(int));

free(intnode);
// C++
int *intnode;
intnode = new int; // new: 명시적인 동적변수 할당
...
delete intnode; // delete: 명시적인 동적 변수 회수
묵시적 힙-동적 변수
•
값이 배정될 때 힙 기억 장소에 바인딩
•
장점: 최고의 유연성 →언제 어떤 타입으로도 변경 가능, 메모리 위치도 자동 변경
•
단점: 모든 동적 속성들을 유지하는데 필요한 실행-시간 부담
# python
highs = [74, 84, 86, 90, 71]
highs = 17.3
// JavaScript
highs = [74, 84, 86, 90, 71];

영역 Scope

변수의 영역

•
변수가 가시적인 문자들의 범위
•
변수가 어떤 문장에서 참조될 수 있으면, 변수는 그 문장에서 가시적

정적 영역

•
변수의 영역이 정적으로 실행 전에 결정
•
인간 프로그래머가 코드를 읽어 모든 변수의 타입 결정 가능
•
정적 영역과 관련하여 두 가지 유형이 존재
◦
부프로그램이 중첩될 수 있는 언어: Python, JavaScript 등
◦
부프로그램이 중첩될 수 없는 언어: C, Java등
JavaScript 에서 함수
function big(){
    functions sub1(){
        var x = 7;
        sub2();
    }
    function sub2(){
        var y = x;
    }
    var x = 3;
    sub1();
}
•
JavaScript는 동적 타입 언어, 정적 영역 언어
•
javascript에서 var은 동적 타입 바인딩
•
javascript는 부프로그램 간의 관계 이용
◦
sub2()의 변수 x에 대한 참조 = big()에서 선언 X
◦
x가 sub2()에 없는 경우 big()에서 탐색 (부모 → 정적)
◦
sub1()에 선언된 x는 무시 (정적 부모가 아니기 때문)
Java
•
Java는 정적 타입 언어, 정적 영역 언어
•
Java의 var은 정적 타입 바인딩
•
중첩된 부프로그램의 허용 여부에서, 어떤 변수 선언은 은폐될 수 있음
•
어떤 언어에서는 은폐된 변수 참조 가능 → this.x

블록

•
많은 언어는 새로운 정적 영역이 실행 코드의 중간에 정의되는 것을 허용
•
지역 변수들은 스택-동적 변수
→ 바인딩이 실행 코드 부분에 진입될 때 할당, 빠져나올 때 회수
•
코드의 부분을 블록 block이라 부름
if(list[i] < list[j]){
    int temp;
    temp = list[i];
    ...
}
void sub(){
    int count;
    ...
    while(...){
        int count;
        count++;
        ...
    }
}
•
sub()의 count는 while루프 내부의 코드로부터 은폐됨
•
C와 C++에서는 이러한 코드 사용이 가능하지만, Java에서는 허용하지 않음 → 오류 방지

선언 순서

void func(){
    ...
    for(int count=0; count<10; count++){
        ...
    }
}
•
함수에 포함된 모든 데이터 선언이, 중첩된 블록에 속한 선언을 제외하고는 그 함수의 시작 부분에 존재해야함
•
C, Java 등에서는 변수 선언의 문장이 위치할 수 있는 어느 곳에서나 허용
◦
복합문이나 부프로그램과 연관되지 않은 영역을 생성
◦
중간에 선언된 지역변수 → 선언문으로부터 그 선언문이 나타나는 블록의 끝까지

전역 변수의 영역

•
파일에 속한 함수 외부에 위치한 변수 정의는 전역변수를 생성 → 모든 함수들에게 가시적
•
C, C++의 경우
◦
전역변수의 선언과 정의를 가짐
◦
전역변수의 정의: 속성 명세, 기억공간의 할당 야기
◦
전역 변수의 선언: 속성 명세, 기억공간의 할당을 야기하지 않음
◦
함수 정의 외부에 위치한 변수 선언은 그 변수가 다른 파일에 정의됨을 명시 extern
예시
day = "Monday"
def tester():
    print("The global day is ", day)
tester() # 정상적 출력: The global day is Monday
day = "Monday"
def tester():
    print("The global day is: ", day)
    day = "Tuesday"
    print("The new value of day is: ", day)
tester() # UnboundLocalError오류 -> tester의 두 번째 day는 지역변수
day = "Monday"
def tester():
    global day
    print("The global day is: ", day)
    day = "Tuesday"
    print("The new value of day is: ", day)
tester() # 정상적 출력

동적 영역

•
부프로그램들의 상호 공간적 배치 관계가 아닌 부프로그램들의 호출 시퀀스에 기반하여 변수의 영역을 결정
•
변수 영역이 동적인 언어들: APL, SNOBOL4, LISP 초기 버전
•
디폴트 영역 매커니즘이 정적, 변수가 동적 영역을 갖도록 선언을 허용: Perl, Common LISP
function big(){
    function sub1(){
        var x = 7;
    }
    functions sub2(){
        var Y = x;
        var z = 3;
    }
    var x = 3;
}
•
sub2()에 참조되는 식별자 x의 의미는 동적
→ 컴파일 시간에 결정 못함
•
호출 시퀀스에 따라 x의 어느 한 선언에 속환 변수를 참조 가능
•
big()이 sub1()을 호출하고 sub1()이 sub2()를 호출
→ sub2()는 sub1()의 x를 참조
•
big()이 sub2()를 직접 호출
→ big()에 선언된 변수 x를 참조

영역과 존속 기간 Scope and Lifetime

변수의 영역과 존속기간은 관련있는 것처럼 보임
•
Java 메소드의 지역변수가 존재하는 경우
◦
지역변수의 영역은 선언문에서 메소드의 끝,
◦
이 변수의 존기간은 실행이 메소드에 진입할 때 시작, 메소드 실행이 종료될 때 끝남
외관상 관계는 다른 상황에서 성립되지 않음
•
C와 C++에서 static을 이용하여 함수에 선언된 변수
◦
함수의 영역으로 바인딩, 기억공간에서 정적으로 바인딩 → 정적, 지역적
◦
존속 기간은 프로그램 전체 실행 시간 → 과격 민감 프로그램 사용 가능
void printheader(){
    ...
}
void compute(){
    int sum;
    ...
    printheader();
}
•
C와 C++에서 함수가 다른 함수를 호출하는 경우
◦
변수 sum의 영역은 compute() 함수 내부에 완전히 포함
◦
compute() 함수가 printheader() 함수를 호출하기는 하지만, sum 영역이 printheader()까지 확장되는 것은 아님
◦
sum의 존속기간은 printheader()가 실행되는 동안의 시간을 걸쳐 확장

참조 환경 Referencing Environments

•
참조 환경은 그 문장에서 가시적인 모든 이름의 집합
정적-영역 언어에서 한 문장의 참조 환경
•
그 지역 영역에서 선언된 변수들
•
그 조상 영역에 속한 가시적인 모든 변수들로 구성
g = 3
def sub1():
    a = 5
    b = 7
    ...        # 프로그램 지점 1
    def sub2():
        global g
        c = 9
        ...      # 프로그램 지점 2
        def sub3():
            nonlocal c
            g = 11
            ...    # 프로그램 지점 3
•
프로그램 지점 1
: 지역변수 a, b(sub1) 참조를 위한 전역변수 g, 배정에 g가 사용될 수 없음
•
프로그램 지점 2
: 지역변수 c(sub2), 참조와 배정을 위한 전역변수 g
•
프로그램 지점3
: 비지역 변수 c(sub2), 지역변수 g(sub3)
동적-영역 언어에서 한 문장의 참조 환경
•
그 지역 영역에 선언된 변수들과 현재 활성화되어 있는 모든 다른 부프로그램에서 선언된 변수들로 구성
•
활성화된 부프로그램에 속한 어떤 변수들은 참조 환경으로부터 은폐뙬 수 있음
void sub1(){
    int a, b;
    ...      // 프로그램 지점 1
}
void sub2(){
    int b, c;
    ...      // 프로그램 지점 2
    sub1();
}
void main(){
    int c, d;
    ...     // 프로그램 지점 3
    sub2();
}
•
프로그램 지점 1
: sub1()의 a, b, sub2()의 c, main()의 d
•
프로그램 지점 2
: sub2()의 b와 c, main()의 d
(main()의 c는 은폐됨)
•
프로그램 지점 3
: main()의 c와 d
Yerim-DevNote
Subscribe to 'Yerim-DevNote'
Subscribe to my site to be the first to receive notifications and emails about the latest updates, including new posts.
Join Slashpage and subscribe to 'Yerim-DevNote'!
Subscribe
👍