본문 바로가기
Language

[Programming Language] 6. 부프로그램(Subprogram) (2)

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

직전글

2023.07.25 - [Language] - [Programming Language] 6. 부프로그램(Subprogram) (1)

 

● 부프로그램의 설계 고려 사항

- 주요 설계 고려 사항들

  1. 지역변수의 정적/동적 할당 여부
  2. 부프로그램의 정의가 중첩(Nested)될 수 있는가에 대한 여부
  3. 매개변수 전달 방법의 선택
  4. 부프로그램의 이름이 매개변수로 전달될 수 있는가에 대한 여부
    • 부프로그램의 이름이 매개변수로 전달되고 부프로그램이 중첩되는 것이 허용된다면, 매개변수로 전달된 부프로그램의 올바른 참조 환경에 대한 문제가 제기됨
  5. 부프로그램을 중복 또는 포괄형 부프로그램으로 허용하는가에 대한 여부
    • 중복 부프로그램 : 동일 참조 환경 내에서 다른 부프로그램과 같은 이름(+ 다른 프로파일)을 갖는 부프로그램
    • 포괄형 부프로그램 : 다른 호출에는 다른 타입의 데이터에 계산을 수행하는 부프로그램
    • 클로저 (Closure) : 중첩 부프로그램 + 중첩 부프로그램의 참조 환경. 그 부프로그램이 프로그램의 임의의 장소에서 호출되는 것을 허용함

● 지역 참조 환경

- 지역변수

부프로그램은 자기 자신의 변수를 정의함. 즉, 지역 참조 환경을 정의함. (부프로그램에서 정의된 변수 == 지역 변수)
앞서 배운 내용에 의하면, 지역 변수는 정적이거나 스택 동적임.

 

ㄴ 스택 동적인 경우


+ 장점
지역 변수는 부프로그램에 유연성을 제공함.
재귀 부프로그램은 스택-동적 지역 변수를 갖는 것이 필수적임.

 

+ 단점
부프로그램이 호출될 때마다 지역 변수를 할당, 초기화, 해제하는 시간 비용이 필요함.
스택 동적 지역 변수의 접근은 간접 주소 방식(스택 위치 + 오프셋)이지만 정적 지역 변수의 접근은 직접 주소 방식임. 모든 지역 변수가 스택 동적이라면, 부프로그램은 과거 민감형이 될 수 없음.

 

ㄴ 정적인 경우

 

+ 장점
스택 동적 지역 변수에 비해 매우 효율적임. (할당과 해제에 실행 시간 오버헤드가 없고, 직접 주소 방식임)
과거 민감형 부프로그램을 허용함.


+ 단점
재귀 호출 부프로그램을 지원하지 못함.

 

대부분의 현대 언어에서 부프로그램의 지역 변수는 디폴트로 스택 동적임.

 

ㄴ C와 C++

지역 변수는 static이라고 표시되지 않으면 스택 동적임.

int addNumInList (int list[], int listLength) {
  static int sum = 0;  // 처음에만 0으로 초기화됨.
  int i;
  for (i = 0; i < listLength; i++)
    sum += list[i];
  return sum;
}

sum은 정적, i는 스택 동적임. 함수 addNumInList()가 불릴 때마다, sum에는 배열 list[]의 값들이 계속 누적되어 저장됨.

 

ㄴ Java

메소드는 스택-동적 지역 변수만을 가짐. 즉, Java의 메소드에는 static으로 표시된 지역 변수가 존재하지 않음.

 

ㄴ Python

Python 메소드에 있는 모든 지역 변수는 스택 동적임.

메소드 정의에서 사용되는 유일한 선언은 전역 변수로, 메소드 외부에서 정의된 변수는 전역으로 선언하지 않고도 메소드에서 참조될 수 있음. 그러나, 그러한 변수에 배정을 하기 위해서는 전역 변수로 선언 (global 키워드 사용) 해야 함.
만일, 전역 변수의 이름을 사용하여 메소드에서 배정을 시행하면, 그 이름의 변수는 지역 변수로 묵시적으로 정의되는 것이며, 전역 변수에 영향을 주지 않음.

 

- 중첩 부프로그램

어느 한 부프로그램 (A)이 다른 부프로그램 (B)에서만 필요하다면, 부프로그램 (A)을 부프로그램 (B) 안에서 정의하고, 프로그램의 다른 부분에 숨기는 것이 당연하다는 생각에서 출발함.

중첩을 허용하는 언어에서, 정적 영역이 일반적으로 사용됨.

중첩 부프로그램의 정적 영역

C 언어에서 파생된 언어들은 부프로그램의 중첩을 허용하지 않음.
Java 8 이전의 Java는 부프로그램의 중첩을 허용하지 않지만, Java 8 부터는 람다식에서 허용됨. (함수형 언어의 특징을 반영한 것)
대부분의 함수형 프로그래밍 언어와 Python, JavaScript 등의 새로운 언어들은 부프로그램의 중첩을 허용함.

 

● 매개변수 전달 방법

매개변수가 피호출 부프로그램에 보내지거나 피호출 부프로그램으로부터 전달받는 방법.

- 매개변수 전달의 의미적 모델

ㄴ 형식 매개변수는 다음 세 개의 의미적 모델이 있음.
(1) 형식 매개변수가 해당 실 매개변수로부터 데이터를 받음 → 입력 모드 (In Mode)
(2) 형식 매개변수가 데이터를 실 매개변수에 전달함  출력 모드 (Out Mode)
(3) 형식 매개변수가 실 매개변수로부터 데이터를 받고, 데이터를 실 매개변수로 전달함.  입출력 모드 (Inout Mode)

 

데이터 이동이 일어나는 방법에는 두 가지 개념적인 모델 존재함.

(1) 실제의 값이 복사(호출자에게, 피호출자에게, 또는 양방향으로)되는 모델
(2) 접근 경로가 전달되는 모델 (가장 일반적인 접근 경로는 단순 포인터이거나 참조)

 

- 매개변수 전달의 구현 모델

앞서 나온 세 개의 기본적인 방법을 위해 개발된 구현 모델들에는 다음과 같은 다섯 가지가 있음.

 

1. 값 전달 (Pass by Value)

입력 모드 의미를 구현하는 것임.
매개변수가 값으로 전달될 때, 실 매개변수의 값은 해당 형식 매개변수를 초기화하기 위해 사용되고, 형식 매개변수는 지역 변수로 사용됨. 보통 복사로서 구현됨.


+ 장점
스칼라 변수에 대해 연결(복사) 비용과 접근(지역변수로 사용) 시간 관점에서 빠름.


+ 단점

형식 매개변수를 위해 부가적인 기억 장소가 호출 프로그램과 피호출 부프로그램 외부의 어떤 장소(관련 내용이 이후에 나올 예정)에 할당되어야 함.
더욱이, 실 매개변수는 해당 형식 매개변수를 위한 기억 장소에 복사되어야 함.
만일 배열처럼 매개 변수가 크다면, 기억 장소의 할당과 복사 연산에 비용이 많이 필요해짐.

 

2. 결과 전달 (Pass by Result)

결과 전달은 출력 모드에 대한 구현 모델임.
매개변수가 결과-전달일 경우, 어떤 값도 부프로그램으로 전달되지 않음.
해당 형식 매개변수는 부프로그램의 지역변수처럼 사용됨.
제어가 호출자에 반환되기 바로 전에, 해당 형식 매개변수의 값이 호출자의 실 매개변수로 전달됨.


+ 장점
스칼라 변수에 대해 연결 비용과 접근 시간 관점에서 빠름. (값 전달과 동일)


+ 단점
값이 복사로 반환된다면, 복사 연산이 필요하므로, 추가적인 기억 장소와 복사 연산을 요구함. (값 전달과 동일)
실 매개변수와 충돌이 가능함.

구현자가 실 매개변수의 주소 평가를 두 개의 다른 시점 (호출 시 또는 복귀 시) 중에서 하나를 선택할 수 있음.

 

C# 메소드 호출 예 - 실 매개변수 충돌

void Example (out int x, out int y) {
  x = 47;
  y = 24;
}

...

f.Example(out a, out a);

만약 Example의 실행 끝에 형식 매개변수 x가 해당 실 매개변수에 먼저 배정된다면, 호출자에 있는 실 매개변수 a의 값은 24가 됨. 반대로, y가 먼저 배정된다면, 호출자에 있는 실 매개변수 a의 값은 47이 됨.

 

이런 순서는 구현 종속적이기 때문에, 구현 버전에 따라 다른 결과가 생성될 수 있음.

 

C# 메소드 호출 예 - 다른 평가 시점

void Assign (out int r, out int i) {
  r = 93;
  i = 11;
}

...

n = 5;
f.Assign(list[n], n);

제어를 시각화한 그림

위의 코드에서, n이 메소드 시작과 끝 사이에서 변화함.
구현자가 이 주소를 메소드 진입 시점에 계산되도록 했다면, 93이 list[5]에 반환됨.
만일, 반환 바로 전에 계산된다면, 93이 list[11]에 반환됨.
이런 문제를 피하는 방법으로, 언어 설계자가 매개변수를 반환하기 위해 사용되는 주소의 계산 시점을 명시하는 방법이 있음.

 

3. 값-결과 전달 (Pass by Value-Result)

값-결과 전달은 값이 복사되는 입출력 모드(Inout Mode) 매개변수의 구현 모델임.

실제로는 값-전달과 결과-전달의 결합으로, 실 매개변수의 값은 대응되는 형식 매개변수를 초기화하는데 사용되고, 형식 매개변수는 지역 변수로 사용됨. 부프로그램 종료 시에 형식 매개변수의 값은 실 매개변수로 반환됨.
때때로 "복사 전달"이라고도 불림.


+ 장점
별칭 상황을 제거 가능함. (관련 내용 이후에 나올 예정) 


+ 단점
값-전달, 결과-전달의 단점을 모두 포함.

 

4. 참조 전달 (Pass by Reference)

참조 전달은 입출력 모드(Inout Mode)  매개변수의 두 번째 구현 모델임.
데이터 값을 서로 간에 복사하는 것 대신에, 접근 경로(또는 주소)를 피호출 부프로그램에 전달함.
따라서, 피호출 부프로그램이 호출 프로그램에 있는 실 매개변수를 접근하는 것을 허용하며, 실제로 피호출 부프로그램은 실 매개변수를 공유함.


+ 장점
전달 과정 자체가 시간과 기억 장소 관점에서 효율적임. (기억 장소 중복이 요구되지도 않고, 복사도 요구되지 않음)

 

+ 단점

형식 매개변수의 접근이 한 단계 이상의 간접 주소를 요구하기 때문에 느림.
피호출 부프로그램에 한 방향 전달 방법만이 요구됨에도 불구하고, 부주의하고 오류적인 변화가 실 매개변수에 행해질 수 있음. (값 전달만을 요구하지만 결과 전달이 행해질 수 있음.)
별칭이 생성될 수 있음. 이는 심각한 문제로, 참조전달이 피호출 부프로그램에게 비지역 변수에 대한 사용 가능한 접근 경로를 제공하기 때문에 발생함. 이런 종류의 별칭은 가독성을 해침으로써 신뢰성에 나쁜 영향을 줄 뿐만아니라, 프로그램 검증을 어렵게 만듦.
별칭이 생성되는 단점은 값-결과-전달을 사용으로 제거될 수 있으나, 값-결과-전달은 앞서 살펴본 바와 같이 다른 단점이 있음에 유의해야 함. → "Trade Off"

 

+ 참조 전달에서 별칭이 생성되는 방법

void fun (int &a, int &b) {
  ...
}

(A) 실 매개변수 사이의 충돌

fun(result, result);

fun 호출에 같은 변수 result를 두 번 쓰면, 참조 변수인 a와 b는 별칭이 됨.

 

(B) 배열 원소 사이의 충돌

fun(list[i], list[j]);

fun 호출에 두 개의 배열 원소를 사용했는데, 우연히 i와 j의 값이 같으면, a와 b는별칭이 됨.

 

(C) 두 개의 형식 매개변수가 하나는 배열의 원소이고 다른 하나는 배열인 경우

fun(list[i], list);

fun1은 두 번째 매개변수로 list의 모든 원소를 접근하는 것이 가능하고, 첫 번째 매개변수로어느 한 원소를 접근하는 것이 가능하기 때문에, 별칭을 생성함.

 

(D) 형식 매개변수와 가시적인 비지역 변수의 충돌

int *global;

void sub(int *param) {...}

void main() {
  ...
  sub(global);
  ...
}

sub에서 param과 global은 별칭임.

 

5. 이름 전달 (Pass-by-Name)

광범위하게 사용되는 언어에서 이름 전달은 사용되지 않아서 더 이상 배우지 않음.

반응형

댓글