서브프로그램 정의
서브프로그램(Subprogram)이란 독자적인 입력과 출력을 갖춘 프로그램 조각을 말한다. 프로그램이 사용자의 입력을 받아 처리를 거친 후 사용가 원하는 출력을 제공하듯, 서브프로그램은 다른 프로그램의 코드로부터 입력을 받고 다른 프로그램 코드에 출력을 제공한다. 이때 서브프로그램의 입력을 인수(Arguments)라고 하고, 서브프로그램의 출력을 반환값(Return Value)라고 한다. 다만 모든 서브프로그램이 인수를 받고 반환값을 내는 것은 아니다. 서브프로그램이 가지는 특징은 다음과 같다.
- 한 개의 입구: 서브프로그램으로 들어가는 입구는 단 한 개이다.
- 여러 개의 출구: 서브프로그램에서 나가는 출구는 여러 곳이 될 수 있다.
- 기본 출구와 반환: 서브프로그램의 맨 끝은 자동적으로 출구가 된다. 다만 별도의 return문을 통해 명시적으로 서브프로그램의 출구를 지정할 수 있다.
- 제어 흐름 이전: 서브프로그램을 호출한 프로그램(호출자)은 호출된 서브프로그램(피호출자)이 수행되기 전에 수행이 정지되며, 호출자에서 피호출자로 제어 흐름이 이전된다.
- 제어 흐름 복귀: 피호출자의 수행이 완료되면 호출자로 제어 흐름이 복귀되며 호출자의 수행이 재개된다.
서브프로그램은 반환값의 유무에 따라 프로시저와 함수로 분류할 수 있다.
- 함수(Function): 반환값이 있는 서브프로그램
- 프로시저(Procedure): 반환값이 없는 서브프로그램, 호출자와 피호출자가 함께 참조할 수 있는 비지역 환경을 변경함으로 피호출자의 실행 효과를 호출자에게 간접적으로 전달한다.
서브프로그램 용어 정리
서브프로그램에서 주로 사용되는 용어들에 대해서 정리하고자 한다.
- 서브프로그램 정의(Definition): 서브프로그램을 호출하였을 때 어떻게 수행되어야 하는지 서브프로그램의 작동을 기술한 부분
- 서브프로그램 호출(Call): 서브프로그램이 수행될 것을 요구하는 부분
- 서브프로그램 헤더(Header): 서브프로그램의 정의 앞부분에서 서브프로그램의 이름, 종류, 매개변수 선언 등을 포함하는 부분, 통상 서브프로그램 정의의 첫 줄이 헤더가 된다.
- 서브프로그램 본체(Body): 서브프로그램의 정의 중, 서브프로그램이 호출되었을 때 실행되는 부분. 서브프로그램의 정의 중 헤더를 제외한 부분이 주로 본체가 된다.
- 매개변수 프로파일(Parameter Profile): 서브프로그램 매개변수의 개수, 순서, 타입을 의미한다.
- 서브프로그램 프로토콜(Protocol): 서브프로그램의 매개변수 프로파일과 더불어 함수의 경우 반환 타입을 포함하여 서브프로그램의 프로토콜이라고 한다.
서브프로그램의 선언과 정의에는 차이가 있는데, 그 차이는 다음과 같다.
- 서브프로그램 선언: 서브프로그램의 프로토콜만 명시
- 서브프로그램 정의: 서브프로그램의 헤더와 본체를 모두 명시
int add(int x, int y); // 서브프로그램(함수) 선언
int add(int x, int y) { // 서브프로그램(함수) 정의
return x + y;
}
매개변수와 인수의 차이에 대해서도 알아보자. 두 용어가 구분 없이 사용되는 경우가 많으나 의도를 명확하게 구분하여 사용하는 것이 좋다.
- 매개변수(Parameter): 서브프로그램 정의 부분에서 선언한 변수, 형식 매개변수(형식인수)라고도 불린다.
- 인수 (Argument): 서브프로그램 호출 부분에서 매개변수로 전달된 의도로 사용된 수식, 실매개변수(실인수)라고 불리기도 한다.
매개변수와 인수 전달
서브프로그램의 매개변수로 인수를 전달할 때에는 매우 복잡한 사항들이 연관되어있기에 인수 전달 모델과 메커니즘에 관해서 잘 알아야 한다. 인수 전달 모델은 의미적 모델, 개념적 모델, 구현 모델로 분류된다.
인수 전달 모델 - 의미적 모델(Sematic Model)
의미적 모델은 실질적으로 프로그래머가 어떠한 용도로 인수를 사용하느냐 하는 관점에서 바라본 인수 전달 모델이다. 의미적 모델에서 인수 전달은 다음의 세 가지 모드(Mode)로 구별된다.
- 입력 모드(In Mode): 호출자의 실인수가 피호출자의 형식 인수로 전달된다.
- 출력 모드(Out Mode): 피호출자의 형식인수가 호출자의 실인수로 전달된다.
- 입출력 모드(In-Out Mode): 호출자와 피호출자 사이의 양방향 전달이 모두 발생된다.
인수 전달 모델 - 개념적 모델(Conceptual Model)
개념적 모델은 자료 이동 측면에서 볼 때 인수 전달 방식이 도식적으로 어떤 방식이지에 관한 인수 전달 모델이다. 인수 전달의 개념적 모델은 크게 "값 전달"과 "참조 전달"로 구분된다.
- 값 전달(Call By Value): 실인수의 값을 물리적으로 복사하여 전달한다.
- 참조 전달(Call By Reference): 실인수의 값을 참조할 수 있는 참조 경로(메모리 주소 값 등)를 전달한다.
인수 전달 모델 - 구현 모델(Implementation Model)
구현 모델은 구체적인 구현 관점에서 프로그래밍 언어가 인수 전달을 어떻게 구현하는지에 따른 인수 전달 모델이다. 인수 전달의 구현 모델은 다섯 가지로 구별할 수 있다.
- 값 전달(Call By Value): 실인수의 값을 형식인수에 복사하여 전달한다.
- 결과 전달(Call By Result): 형식인수의 값을 실인수에 복사하여 전달한다.
- 값-결과 전달(Call By Value-Result): 호출 시 실인수 값을 형식인수에 복사하고 복귀 시 형식인수 값을 실인수 값에 복사하여 전달한다.
- 참조 전달(Call By Reference): 실인수에 대한 참조 경로를 전달하여 형식인수를 참조할 때마다 실인수 위치를 참조할 수 있도록 한다.
- 이름 전달(Call By Name): 실인수 이름을 전달하여 형식인수를 사용할 때마다 실제 실인수 이름이 사용되는 것과 같은 효과를 내도록 한다.
대다수의 프로그래밍 언어에서는 다음 한 두 개의 인수 전달 방법을 채택한다.
서브프로그램 확장
서브프로그램은 구조화 프로그래밍 기법과 더불어 프로그램 모듈의 핵심으로 발전헀다. 작업을 분할하여 세부 작업을 구현하는 데 매우 뛰어나며 또한 코드 재사용 개념을 제시하며 생산성을 매우 높였다. 이에 따라 서브프로그램을 더욱 확대하고자 하는 움직임이 일어났다.
범용 서브프로그램 (Generic Subprogram)
범용 서브프로그램이란 여러 타입의 인수에 대해 동작할 수 있는 서브프로그램을 말한다. 여러 타입을 다룰 수 있는 기능을 다형성(Polymorphism)이라고 하는데, 범용 서브프로그램은 데이터 구조에 확대되어 타입을 매개 변수로 받는 자료구조인 다형 자료구조라 확대되었다. 대표적인 다형 자료구조로는 C++의 템플릿(Template), Java의 제네릭(Generic) 등이 잇다.
분리 컴파일, 독립 컴파일(Seperate / Independent Compilation)
서브프로그램의 대표적인 목적은 프로그램 모듈화이다. 프로그램 모듈화는 프로그램에서 처리할 큰 작업을 여러 개의 작은 작업으로 나누어 프로그램을 작성함으로 효율을 높이는 업무 방식이다. 이를 위하여 프로그래밍 언어는 분리 컴파일이나 독립 컴파일을 지원한다.
- 분리 컴파일(Seperate Compilation): 코드 사이의 인터페이스 정보를 이용하며 프로그램 일부를 다른 부분과 분리하여 컴파일하는 방법
- 독립 컴파일(Independent Compilation): 인터페이스 정보 없이 프로그램 일부를 다른 부분과 분리하여 컴파일하는 방법
최근에는 두 용어 구분없이 모두 분리 컴파일이라고 부르기도 한다.
서브프로그램 구현
서브프로그램을 정의하는 이유는 결국 서브프로그램을 호출하기 위해서이다. 프로그램 A가 서브프로그램 B를 호출할 때 A를 호출자, B를 피호출자라고 한다. 서브프로그램을 호출하는 작업과 서브프로그램으로부터 복귀하는 작업을 합쳐서 "서브프로그램 연결(Subprogram Linkage)"라고 한다. 서브프로그램 연결을 성공적으로 마치기 위해서는 "서브프로그램 호출 시 해야하는 작업"과 "서브프로그램 복귀 시 수행해야 할 작업"을 모두 정상적으로 진행해야 한다. 먼저, 서브프로그램 호출 시 해야 하는 작업은 다음과 같다.
- 호출자의 상태 저장: 매개변수, 지역변수. 레지스터 내용 등을 저장해야 한다.
- 인수 전달: 인수 전달 방법에 따라 인수 값 혹은 주소를 호출자 환경에서 계산하여 피호출자로 전달한다.
- 복귀 주소 전달: 피호출자 수행을 마치고 복귀해야 할 호출자의 코드 주소를 저장한다.
- 피호출자로 분기: 피호출자의 코드 시작 부분으로 분기한다.
서브프로그램 복귀 시 수행해야 할 작업은 다음과 같다.
- 형식인수 값 복사:출력 모드나 입출력 모드 인수의 경우 형식인수값을 실인수로 복사한다.
- 반환값 전달: 함수의 경우 반환값을 호출자의 환경으로 전달해야 한다.
- 상태 복귀: 호출자의 상태(레지스터, 지역변수 등)를 회복시킨다.
- 호출자로 복귀: 저장해둔 복귀 주소로 분기한다.
활성 레코드 (Activation Record)
서브프로그램 연결 시 자료 흐름을 관리하기 위하여 활성 레코드를 사용한다. 활성 레코드란 수행 중인 서브프로그램의 코드를 제외한 데이터 부분이 저장되는 형태를 말한다. 서브프로그램의 데이터 뿐만 아니라 호출자의 상태, 복귀 주소, 반환 값 등 각종 관리 데이터가 저장된다. 활성 레코드에는 영역 규칙을 구현하기 위해 중요한 정보가 저장된다. 이때 "동적 링크"와 "정적 링크"가 주로 사용되며, 동적 링크가 연결된 형태를 "동적 체인"이라고 하고 정적 링크가 연결된 형태를 "정적 체인"이라고 부른다.
정적 체인과 동적 체인 (Static Chain, Dynamic Chain)
- 정적 체인(Static Chain): 활성 레코드 스택 상에서 정적 링크들을 차례로 연결한 것으로 트리 형태를 이룬다. 정적 영역 규칙을 구현하기 위하여 사용되며, 정적 체인 방법에서는 어떤 변수를 (체인 변위, 지역 변위) 쌍으로 나타낼 수 있다. 변수를 참조하기 위해서는 체인 변위만큼 정적 링크를 따라간 후에 해당 활성 레코드에서 지역 변위를 가진다.
- 동적 체인(Dynamic Chain): 활성 레코드 스택 상에서 인접한 동적 링크들을 차례로 연결한 것으로 리스트 형태를 이룬다. 동적 영역 규칙을 구현하기 위하여 사용되며, 동적 체인 방법에서는 원하는 변수를 찾을 때까지 동적 체인을 계속 거슬러 올라간다. 따라서 활성 레코드에 이름 정보를 저장해야 하며 실시간 탐색이 필요하다.
다양한 서브프로그램 구현 방법
별도의 자료구조를 도입하여 체인을 따라가는 부담을 없애고 서브프로그램을 구현하는 방법들이 제시되었다. 다양한 서브프로그램 구현 방법은 다음과 같다.
- 디스플레이(Display): 자신을 포함한 자신의 모든 정적 조상의 활성 레코드에 대한 포인터가 저장되어있는데, 이를 통해 원하는 위치의 활성 레코드를 직접 참조할 수 있다. (이때 모든 변수에 대한 정보를 한 곳에서 관리하여 이를 통해 변수를 참조하는 방법을 얕은 참조, 동적 체인을 거슬러 올라가는 방법을 깊은 참조라고 한다.)
- 과거 민감 서브프로그램(History-Sensitive Subprogram): 이전의 호출 내용을 기억하는 서브프로그램이다. 캡슐화를 높이기 위해 정적 지역변수를 이용하여 구현하는 것이 좋다.
- 코루틴(Coroutine): 여러 개의 진입 지점을 스스로 관리하는 프로그램이다. 호출자와 피호출자가 서로 대응한 관계를 이루는 대칭적 제어 모델을 형성한다.