본문 바로가기
Language

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

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

직전글

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

 

- 매개변수 전달 방법의 구현

대부분의 현대 프로그래밍 언어에서 매개변수 전달은 런타임 스택(run-time stack)을 통하여 이루어짐.
런타임 스택은 시스템 프로그램에 의하여 초기화되고 유지 관리됨.
런타임 스택은 부프로그램 제어 연결과 매개변수 전달을 위해 광범위하게 사용됨. (이후 관련 내용 나올 예정)
이하에서는 스택을 통하여 모든 매개변수가 전달된다고 가정함.

 

1. 값 전달 매개변수

값을 스택 장소에 복사하고, 스택 장소는 대응되는 형식 매개변수의 기억장소로 사용됨.

 

2. 결과 전달 매개변수

값 전달의 반대로 구현함. 결과 전달 실 매개변수에 배정된 값이 스택에 놓여짐. 피호출 프로그램의 종료 시에 호출 프로그램에 의해 추출됨.

 

3. 값-결과 전달 매개변수

값-전달과 결과-전달의 결합으로 구현됨. 매개 변수를 위한 스택 장소는 호출에 의해서 초기화 되고 매개변수는 비호출 부프로그램에서 지역 변수처럼 사용됨.

 

4. 참조 전달 매개변수

실 매개변수의 타입에 관계없이 단지 실 매개변수의 주소가 스택에 놓여짐.

리터럴인 경우에는 리터럴의 주소가 스택에 놓여짐.

표현식인 경우에는 피호출 부프로그램으로 제어가 전달되기 전에 그 식을 평가하는 코드를 컴파일러가 생성하고, 그 표현식의 결과 값이 저장된 메모리 셀의 주소를 스택에 놓음.
리터럴과 표현식의 경우, 호출된 부프로그램이 값을 변경하는 것을 컴파일러가 막아야 함.

 

피호출 부프로그램에서 형식 매개변수의 접근은 간접 주소 방식에 의해서 스택에서 이루어짐.

ㄴ 그림 예시

네 종류의 매개변수 구현 예시

부프로그램 sub가 main으로부터 sub(w, x, y, z)로 호출한 상황임.

여기서 w는 값 전달, x는 결과 전달, y는 값-결과 전달, z는 참조 전달됨.

 

- 일반적인 언어의 매개변수 전달 방법

ㄴ C

값 전달을 사용함.
C에서의 참조 전달은 매개변수로 포인터를 사용함으로써 이루어짐. → 결국 근본적으로는 값 전달임.
포인터 값은 피호출 함수에서 사용할 수 있고, 아무것도 반환하지 않음.
그러나, 전달되는 것은 호출자의 데이터에 대한 주소이므로, 피호출 함수는 호출자의 데이터를 변경할 수 있음.

 

ㄴ C++

기본적으로 값 전달을 사용하며, 매개변수에 참조 타입이라는 특수한 포인터 타입을 포함함.
참조 매개변수는 함수나 메소드에서 묵시적으로 역참조 됨. → 참조-전달의 의미.

void fun (const int &p1, int p2, int &p3) { … }

p1은 참조 전달이지만 fun에서 변경 불가능함, p2는 값 전달, p3는 참조 전달임.
p1과 p3는 fun에서 명시적으로 역참조할 필요가 없음. (즉, C언어처럼 '*'를 쓸 필요가 없음.)

 

ㄴ Java

C와 C++처럼, 모든 Java 매개변수는 값-전달임.
그러나, 객체는 참조 변수로만 접근되므로, 객체 매개변수는 본질적으로 참조 전달의 의미임.
매개변수로 전달된 객체 참조는 피호출 부프로그램에서 변경될 수 없음.
하지만, 참조된 객체는 객체의 메소드를 통해 객체에 속한 값을 변경할 수 있음.
Java는 포인터 타입을 제공하지 않으므로, Java에서 스칼라는 참조로 전달될 수 없음. 스칼라를 포함하는 객체는 가능함. (int 변수를 참조로 넘기지는 못해도, Integer 객체는 가능하다는 뜻)

 

+ Swap 함수 예시

Swap (A a, A b) {
  A temp = a;
  a = b;
  b = temp;
}

...

Swap (c, d);

Swap 함수 예시에 대한 그림 설명

ㄴ Python

Python에서 사용되는 매개변수 전달 방법을 배정 전달이라고 부르며, 배정 전달은 Java처럼 "참조 변수에 대한 값 전달"과 유사하지만 차이점이 있음.
Python의 모든 변수는 객체에 대한 참조 변수임. 배정 전달에서 실 매개변수의 값은 값 전달처럼 형식 매개변수에 배정됨. 결국은 참조 전달을 의미함. (모든 실 매개변수의 값은 참조이기 때문)
그러나, Python에서 많은 객체들은 본질적으로 불변임.


+ 변수 예시

x = x + 1

예시 그림 설명

x가 참조하는 객체로부터 값을 가져와서 1을 증가시킨 다음, 그 증가된 값을 저장하는 새로운 객체를 생성하고, x가 새로운 객체를 참조하도록 변경함.
따라서, 스칼라 객체에 대한 참조가 부프로그램에 전달될 때, 호출자의 객체는 변경되지 않음. 왜냐하면, 참조는 값으로 전달되기 때문임.

 

+ 배열 예시

list[3] = 47

형식 매개변수가 새로운 배열 객체로 배정된다면, 호출자에게는 아무런 영향 없음.
그러나, 형식 매개변수가 위와 같이 배열 원소의 값에 배정 연산을 수행한다면 호출자의 실 매개변수는 영향을 받음.

예시 그림 설명

 

정리하면, 형식 매개변수의 참조를 변경하는 것은 호출자에게 영향을 주지 않지만, 매개변수로 전달된 배열의 원소를 변경하는 것은 영향을 줌.

 

- 매개변수로서 다차원 배열

ㄴ C에서 행렬을 함수로 전달하는 방법

C에서 다차원 배열은 실제로 배열의 배열이고, 행 우선 순서로 저장함.
다음은 모든 하한이 0이고 원소의 크기가 1인 행렬의 행 우선 순서를 위한 기억장소 사상 함수임.

address(mat[i,j]) = address(mat[0,0]) + i * number_of_columns + j;

이 사상 함수는 열의 개수는 필요로 하지만, 행의 개수는 필요로 하지 않음.
이는 C와 C++에서 행렬을 매개변수로 전달할 때, 매개변수는 두 번째 대괄호 안에 열의 개수를 포함해야 하는 이유임.

 

위 함수의 문제점은 프로그래머에게 열의 개수가 다른 행렬을 수락하는 함수를 작성하는 것은 허용하지 않는다는 것임.

즉, 열의 개수가 다른 행렬에 대해서는 새로운 함수를 작성해야 함.

이는 포인터 연산을 이용하여 해결가능함. 행렬은 포인터로 전달되고, 행렬의 실제 차원은 매개변수로 포함시킴.

void fun(float *mat_ptr, int num_rows, int num_cols) {
  ...
  // 변수 x의 값을 [i][j] 원소로 이동하는 문장
  *(mat_ptr + (i * num_cols) + j) = x;
  ...
}

 

ㄴ Java에서 다차원 배열을 메소드로 전달하는 방법

Java에서 배열은 객체임. 배열은 모두 1차원이지만, 원소가 배열이 될 수 있음.
각 배열은 배열 객체가 생성될 때, 배열의 길이로 설정된 이름 상수(length)를 상속받음.
행렬의 형식 매개변수는 두 개의 빈 대괄호로 표현됨.


+ 예시

float sumer (float mat[][]) {
  float sum = 0.0f;
  for (int row = 0; row < mat.length; row++) {
    for (int col = 0; col < mat[row].length; col++) {
      sum += mat[row][col];
    }
  }
  return sum;
}

각 배열은 자신의 길이 값을 가지므로, 행렬에서 각 행은 다른 길이를 가질 수 있음.

 

- 매개변수 전달의 예제

ㄴ C

void swap1 (int a, int b) {
  int temp = a;
  a = b;
  b = temp;
}

...

swap1 (c, d);

위 코드의 동작을 의사 코드(Pseudo Code)로 서술하면 다음과 같음.

a = c
b = d
temp = a
a = b
b = temp

아무것도 호출자에게 전달되지 않기 때문에, c와 d의 값은 변하지 않음.

 

참조 전달의 효과를 얻기 위해 포인터를 사용하면 다음과 같음.

void swap2 (int *a, int *b) {
  int temp = *a;
  *a = *b;
  *b = temp;
}

...

swap1(&c, &d);

위 코드의 동작을 의사 코드로 서술하면 다음과 같음.

a = &c
b = &d
temp = *a
*a = *b
*b = temp

c와 d의 값은 실제로 서로 교환됨.

 

이 연산은 Java에서 불가능함. (Java는 포인터도 없고, 참조 매개변수도 없기 때문임)
a와 b는 호출자의 c와 d의 참조 값을 서로 바꾸어 저장하기는 하지만, c와 d에는 아무 영향 없음.

Java에서 참조 변수는 스칼라 값이 아니라 단지 객체만 가리킴.

 

● 부프로그램 매개변수

부프로그램의 이름이 다른 부프로그램의 매개변수로 전달된다면, 여러 상황에서 편리해질 수 있음.

예를 들어, 적분을 수행하는 부프로그램은 여러 점에서 함수 값을 샘플링하면서, 그 함수 그래프로 이루어진 넓이를 추정하여야 함. 그러한 부프로그램이 작성된다면, 그 부프로그램은 임의의 주어진 함수에서 사용이 가능해야 함.

즉, 적분할 모든 함수에 대해 부프로그램을 다시 작성할 필요가 없어야 함. 따라서, 적분할 수학 함수의 이름을 적분 부프로그램에 매개변수로 전달하는 것이 자연스러움.

C와 C++에서는 함수 포인터를 사용하여 이러한 문제를 해결함.

 

● 간접 부프로그램 호출

부프로그램이 간접적으로 호출되어야 하는 경우가 존재함.
이러한 상황은 호출될 특수한 부프로그램이 실행될 때까지 알려지지 않을 때 자주 발생함.
이러한 부프로그램의 호출은, (호출이 이루어지기 전에 설정되는), 부프로그램에 대한 포인터나 참조를 통해 이루어짐.
간접 부프로그램 호출의 가장 일반적인 두 가지 예로 GUI에서 (1) 이벤트 처리 (Event Handling)와 (2) 콜백 (Callback) 이 있음.
(1) 이벤트 처리
프로그램 실행 중 발생하는 이벤트에 대한 조치. 거의 모든 Web 응용에서 사용되고 있으며, 많은 비Web 응용에서도 사용되고 있음.

ex. 여러 입력 장치들
(2) 콜백
콜백은 어떤 부프로그램이 호출되고, 호출된 부프로그램이 작업을 종료했을 때, 호출자에게 알리는 이벤트를 의미함.

ex. 타이머

부프로그램을 간접적으로 호출한다는 개념은 최근에 개발된 것은 아님.
C와 C++에서 함수의 포인터를 이용하여 함수를 간접적으로 호출할 수 있음.

int myfun2 (int, int); // 함수 선언
int (*pfun2) (int, int); // 함수 포인터를 생성

...

pfun2 = myfun2; // 함수의 주소를 포인터에 배정

...

(*pfun2)(first, second); // 함수의 포인터로 함수를 호출하는 방법 1
// 포인터를 역참조하는 방법

...

pfun2(first, second); // 함수의 포인터로 함수를 호출하는 방법 2
// 포인터 역참조 없이도 사용 가능

C와 마찬가지로, Python에서 소괄호가 없는 함수 이름은 함수 포인터임.

def fun1(x):
  print("one: " + x)
  
def fun2(x):
  print("two: " + x)
  
f = fun1
f("paino")

f = fun2
f("violin")

# 출력
# one: paino
# two: violin

Java는 포인터를 가지지 않음.

반응형

댓글