본문 바로가기
Language

[Programming Language] 2. 변수, 바인딩, 영역

by 삼준 2023. 7. 9.
반응형

직전글

2023.07.07 - [Language] - [Programming Language] 1. 서론

 

● 변수 (Variable)

컴퓨터 메모리 셀이나 셀들의 모임에 대한 추상화.

흔히 메모리 위치에 대한 이름으로 생각하지만, 변수는 이름 외에도 몇 가지 속성을 더 가짐. (총 6개의 속성을 다룰 예정)

 

1. 이름

변수의 가장 기본적인 속성으로, 어떤 개체를 식별하기 위해 사용되는 문자열임.

식별자(Identifier)라는 용어와 혼용되어 사용됨.

ㄴ 형식

길이 제한 : 언어 마다 제한하는 경우도 있고 안하는 경우도 있음.

표기법 : 낙타 표기법(myVariable), 스네이크 표기법(my_variable) 등 다양한 방법이 존재함.

대소문자 구분 : 많은 언어에서 대소문자를 구분함.(== 다르게 인식함.)

 

2. 주소

변수와 연관된 메모리 주소

동일한 변수라도, 프로그램의 다른 시기에 다른 주소를 배정 받을 수 있음. (추후 다시 언급 예정)

L-value라고도 불림. (변수가 배정문의 좌측에 나타날 때 주소가 요구되기 때문임)

ㄴ 별칭(Alias)

동일한 주소를 갖는 여러개의 변수가 존재 가능한데, 이처럼 두 개 이상의 변수 이름이 동일한 메모리 위치를 접근하는데 사용 가능하면 그 변수들을 별칭이라고 부름.

별칭은 변수의 값이 다른 변수에 대한 배정문에 의해 변경되는 것을 허용함.

+ 별칭 생성 방법 및 예

  • C) 공용체(union) 사용
  • C) 두 개의 포인터 변수가 동일한 메모리 위치를 가르키는 경우
  • Java, Python) 참조 변수들

3.  타입

변수의 타입은 두 가지를 결정함.

  • 변수가 저장할 수 있는 값의 범위
  • 해당 타입의 값에 대해 정의되는 연산들의 집합

4. 값

변수와 연관된 메모리 셀이나 셀들의 내용

R-value라고도 불림. (변수가 배정문의 우측에 나타날 때 값이 요구되기 때문임)

 

5. 존속기간

아래 "바인딩"에서 다룰 예정

 

6. 영역

아래 "영역"에서 다룰 예정

 

● 바인딩 (Binding)

속성(Attribute)과 엔티티(Entity) 사이의 연관(Association)

- 예시

  • 변수와 타입 사이의 연관 -> 컴파일 시점에 바인딩됨.
  • 변수와 값 사이의 연관 -> 실행 시점에 바인딩됨.
  • 변수와 주소 사이의 연관 -> 메모리에 로드되는 시점에 바인딩됨.
  • 심볼과 연산 사이의 연관 -> 언어 설계 시점에 바인딩됨.
  • 부프로그램(ex. 함수) 호출과 부프로그램 코드와의 연관 -> 링크 시점에 바인딩됨.

- 정적 바인딩(Static Binding)과 동적 바인딩(Dynamic Binding)

정적 바인딩 : 실행 시간이 시작되기 전에 일어나고, 실행 전체에 걸쳐 변하지 않는 바인딩

동적 바인딩 : 실행 시간 중에 일어나고, 실행 과정에서 변경될 수 있는 바인딩

 

- 타입 바인딩

변수는 프로그램에서 참조되기 전 타입이 바인딩 되어야 하며, 정적 타입 바인딩과 동적 타입 바인딩 두 종류가 존재함.

ㄴ 정적 타입 바인딩

타입이 프로그램 단위의 존속기간 동안 고정됨. 명시적 선언과 묵시적 선언은 모두 정적 타입 바인딩을 생성함.

정적 타입 바인딩 언어) C, C++, Java 등

+ 명시적 선언 : 변수 이름을 나열하고 특정 타입이라는 것을 명세함. (ex. C - int i;)

+ 묵시적 선언 : 선언문 보다는 디폴트 규칙을 통해 타입을 연관 시킴. (ex. Fortran - I, J, K, L, M, N으로 시작하면 Integer 타입임)

※ 형변환은 값이 동적으로 변해서 대입되는 것으로, 변수 자체의 타입은 변함 없으므로 정적이라 할 수 있음.

 

ㄴ 동적 타입 바인딩

타입이 명세되지 않으며, 이름의 철자로부터 결정되지 않음. 명시적 선언, 묵시적 선언 둘 다 아님.

대신, 변수에 값이 할당될 때 타입이 바인딩됨. 더불어, 그러한 배정문은 그 변수를 다른 주소나 메모리 셀에 바인딩할 수도 있음. (해당 값이 다른 크기의 기억 공간을 요구할 수 있기 때문)

변수의 타입은 프로그램 실행 중 여러번 변경될 수 있으므로, 일시적임을 기억해야 함.

동적 타입 바인딩 언어) Python, JavaScript, Ruby, PHP 등

+ 장점

프로그래밍 유연성 (임의의 수치 타입의 데이터를 다룰 수 있는 포괄적 프로그래밍이 가능함.)

+ 단점

프로그램을 덜 신뢰적으로 만듦.

실행 시간에 타입 검사를 수행하기 때문에 실행 속도가 느려질 수 있음.

변수의 현재 타입을 유지하고 관리하기 위한 메모리 비용이 발생함.

 

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

!! 명령형 프로그래밍 언어의 근본적인 특성은 변수들의 기억공간 바인딩 설계에 의해 많은 부분이 결정되므로 잘 이해해두는 것이 중요함.

할당과 회수 : 변수에 바인딩되는 메모리 셀을 가용 메모리 풀에서 가져오는 과정을 할당, 바인딩이 해제된 메모리 셀을 다시 가용 메모리 풀에 반환하는 과정을 회수 라고 함.

존속 기간 : 변수가 특정 메모리 위치에 바인딩되어 있는 기간. (변수가 특정 셀에 바인딩될 때 ~ 바인딩이 해제될 때)

 

ㄴ 존속 기간에 따른 변수들의 4가지 분류

1. 정적 변수 {대표 : C언어 전역변수}

프로그램 실행 시작 전부터 종료될 때까지 동일한 메모리 셀에 바인딩 됨.

+ 예시 : 전역변수, 지역적 정적 변수(History sensitive한 부프로그램에서 사용)

+ 장점 : 정적 변수의 주소지정은 직접적임, 실행 시간에 할당과 회수를 하지 않아도 됨 => 효율적임.

+ 단점 : 유연성이 낮음 (재귀적 프로그램 사용 불가), 기억공간이 변수들 간에 공유될 수 없음.

사용 방법 : C,C++) static 키워드 사용 / Java, C++, C# 의 Class) 클래스 변수(static 키워드를 사용)

 

2. 스택-동적 변수 {대표 : C언어 지역변수}

변수의 선언문이 실행될 때, 기억공간에 바인딩 되지만 타입은 정적으로 바인딩 됨.

+ 예시 : 함수나 메소드의 경우, 시작부분에서 변수가 선언되면 함수가 호출될 때 할당, 종료될 때 회수 되며, 중간부분에서 변수가 선언되면 함수가 호출될 때 할당되긴 하지만, 변수 선언 부분이 실행될 때까지 가시화되지 않아 사용이 불가능함.

+ 장점 : 유연성 증가 (재귀적 프로그램에 사용 가능)

+ 단점 : 할당과 회수에 따른 실행시간 부담, 간접 주소지정 방식이라 접근이 느림 => 비효율적임, History sensitive한 부프로그램에서 사용할 수 없음.

 

3. 명시적 힙-동적 변수 {대표 : C언어 동적할당}

프로그래머가 명세하는 명령어에 의해 실행 시간에 할당되고 회수되는 이름이 없는 메모리 셀을 의미함.

힙(heap)으로부터 할당, 회수됨.

포인터나 참조 변수를 통해 참조될 수 있음.

+ 예시 : C) malloc() 함수 / C++,Java) new 연산자

+ 장점 : 동적 구조체 구현 가능(연결리스트, 트리 - 내용 추가 삭제가 자유로운 자료구조)

+ 단점 : 올바른 사용의 어려움(포인터, 참조변수), 기억공간 관리 구현의 복잡성

 

4. 묵시적 힙-동적 변수 {대표 : 파이썬 변수}

값이 배정될 때 힙 기억장소에 바인딩 됨.

+ 예시 : Python) i = 10

+ 장점 : 최고의 유연성 (언제든 타입 변경 가능, 메모리 위치 자동 변경)

+ 단점 : 모든 동적 속성을 유지하는데 필요한 실행시간 부담

 

● 영역 (Scope)

변수가 가시적(Visible)인 문장들의 범위

변수가 어떤 문장에서 참조될 수 있으면(== 메모리에서 찾을 수 있으면), 그 문장에서 가시적이라고 함.

- 정적 영역

변수의 영역이 부프로그램들의 배치에 의해 실행 전에 결정됨. 따라서 인간이 코드를 읽어서 모든 변수의 타입을 결정 가능함. 상위 계층에 있는 함수가 부모임.

정적 영역과 관련해 언어를 분류하면 두 가지 유형이 존재하게 됨.

부프로그램이 중첩될(== 함수 안에 함수를 선언할) 수 있는 언어 : Python, JavaScript 등

부프로그램이 중첩될 수 없는 언어 : C, Java 등

ㄴ JavaScript 중첩 함수 예시

function sup() {

  function sub1() {
    var x = 10;
    sub2();
  }
  
  function sub2() {
    var y = x;
  }
  
  var x = 1;
  sub1();
}

위의 코드에서 sub2()의 변수 x에 대한 참조는 함수 sup()에 선언된 x를 참조하게 됨.

x가 sub2에 없는 경우, 정적 부모인 sup() 함수에서 탐색이 이루어지기 때문임.

 

중첩된 부프로그램의 허용 여부와 관계 없이, 어떤 변수 선언은 다른 코드 세그먼트로부터 은폐될 수 있음.

위의 예시에서 sub1() 함수의 x 선언에 의해 sub1() 함수에서 big() 함수의 x는 은폐됨.

하지만 어떤 언어에서는 은폐된 변수를 참조 가능하기도 함.

 

- 블록

실행 코드의 중간에 정의되는 새로운 정적 영역.

즉, 코드의 일부분이 각자의 지역 변수를 갖는 것을 허용함. 이 때 지역 변수들은 전형적으로 스택-동적 변수임. 변수와 기억공간 바인딩이 실행이 코드 부분에 진입할 때 할당되고 코드 부분을 빠져나올 때 회수됨.

예시 : C) 복합문(== 중괄호에 둘러싸인 문장들)이 선언문을 갖는 것

 

- 선언 순서

일부 언어들에서는 모든 선언이 함수의 시작 부분에 존재해야함.

그렇지 않은 언어들에서, 중간에 선언된 지역변수의 영역은 선언문으로부터 선언문이 나타나는 블록의 끝까지임.

 

- 전역 변수의 영역

파일에 속한 함수 외부에 위치한 변수 정의는 전역 변수를 생성하고, 모든 함수들에게 가시적임.

+ 예시1 : C & C++에서 전역 변수의 선언/정의

전역 변수의 정의 = 변수의 속성들을 명세하고, 기억공간의 할당을 야기함. ( int i; )

전역 변수의 선언 = 변수의 속성들을 명세하지만, 기억공간의 할당을 야기하진 않음. ( extern int i; )

함수 정의 외부에 위치한 변수 선언은 그 변수가 다른 파일에 정의되어 있음을 명세함.

+ 예시2 : Python의 특이성

전역 변수는 함수에서 참조될 수 있지만, 전역적이라고 선언된 경우(global)에만 새로운 값을 할당 가능함.

# 문제없음
day = "Monday"
def tester():
  print("The global day is:", day)
tester()

######################################

# UnboundLocalError 발생
# tester()의 두번째 day는 지역변수이기 때문
day = "Monday"
def tester():
  print("The global day is:", day)
  day = "Tuesday"
  print("The new global day is:", day)
tester()

######################################

# 문제없음
day = "Monday"
def tester():
  print("The global day is:", day)
  day = "Tuesday"
  print("The new global day is:", day)
tester()

 

- 동적 영역

동적 영역은 부프로그램들의 호출 순서에 기반해 변수의 영역을 결정함. 먼저 호출된 함수가 부모임.

 

- 영역과 존속 기간

종종 영역과 존속 기간은 관련되어 있는 것처럼 보임. => 부프로그램 내부의 지역 변수 영역은 선언문에서부터 메소드의 끝이고, 존속 기간은 메소드 진입할 때 시작해서 메소드 실행 종료될 때 끝남.

하지만, 다른 상황에서는 성립되지 않음. => C언어 static 변수의 영역은 정적이고 기억공간에 정적으로 바인딩 되지만, 존속기간은 함수가 속한 프로그램 전체 실행 시간에 걸쳐 확장 됨.

아래는 또 다른 예시.

void printheader() {
  ...
}

void compute() {
  int sum;
  ...
  printheader();
}

// sum의 영역은 compute 함수 내부만이지만, 존속 기간은 printheader의 실행 시간을 포함함.

- 참조 환경

어느 문장에서 가시적인 모든 이름의 집합.

정적 영역 언어에서 한 문장의 참조 환경은 (1) 그 지역 영역에 선언된 변수와, (2) 그 조상 영역에 속한 가시적인 모든 변수들로 구성됨.

# Python 코드 예시
g = 3
def sub1():
  a = 4
  b = 5
  ...        # 지점 1
  def sub2():
    global g
    c = 6
    ...      # 지점2
    def sub3():
      nonlocal c
      g = 7
      ...    # 지점3

지점1) 지역 변수 a, b, 참조를 위한 전역 변수 g (배정 불가)

지점2) 지역 변수 c, 참조와 배정을 위한 전역 변수 g

지점3) 비지역 변수 c, 지역 변수 g

 

동적 영역 언어에서 한 문장의 참조 환경은 (1) 그 지역 영역에 선언된 변수와, (2) 현재 활성화되어 있는 모든 다른 부프로그램에서 선언된 변수들로 구성됨. 활성화된 부프로그램에 속한 어떤 변수들은 참조 환경으로부터 은폐될 수 있음.(동일 이름 존재시)

// 임의의 동적 영역 언어 프로그램
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

반응형

댓글