함수
프로그래밍에서 함수는 인자를 통해 입력을 받고(인자가 없는 경우도 있지만), 이를 함수 내부 문장을 통해 처리해 결과를 반환한다. 프로그래밍 언어에서 함수에 리턴 값이 없다면 이를 프로시저(Procedure)라 한다. 프로시저는 일련의 계산 과정을 추상화한 개념으로 리턴 값이 없는 대신 매개변수나 비지역 변수를 변경함으로써 계산 결과를 나타낸다. 언어 S의 함수와 관련된 구문을 살펴보자.
<command> -> <decl> | <stmt> | <function>
<function> -> fun <type> id( <params> ) <stmt>
<stmt> -> ... | id( <expr> {, <expr>} ); | return <expr>;
<factor> -> ... | id( <expr> {, <expr>} )
이중 id( <expr>{, <expr> } );은 함수 호출을, return <expr>;은 우리에게 익숙한 리턴문이다. 마지막으로 <function>에 기술된 문장은 함수 정의 구문이다. 하나씩 살펴보자.
<function>의 함수 정의는 새로운 함수를 정의하기 위한 것으로 fun으로 시작한다. 함수의 정의는 함수 헤더(header)와 본체(body)로 이루어지며 함수 헤더에는 리턴 타입 <type>과 함수 이름 id, 매개변수 ( <params> )를 선언한다. 이후 함수의 본체 문장인 <stmt>를 기술한다.
함수 호출은 함수 이름과 인자들로 구성된다. id( <expr> {, <expr>} )의 id는 함수의 이름이며 괄호 내부는 매개변수에 해당하는 수식 Expr이다. 함수 호출은 리턴 값이 없는 경우에는 하나의 문장(statement)으로 사용될 수 잇으며, 리턴 값이 존재한다면 함수 호출은 하나의 값이므로 이 경우의 함수 호출은 하나의 인수(factor)에 해당한다.
>>> int a = 10;
>>> int b = 5;
>>>fun int max(int x, int y)
if (x > y) then return x;
else return y;
>>>print max(a, b);
위 문장에서는 Line 3에서 함수 max를 정의하고 있으며 Line 6에서 함수 max를 호출하여 사용하고 있다. 이때 max는 값을 리턴하므로 하나의 인수 factor에 해당한다.
여러 언어의 함수 정의
Lisp, Scheme, JavaScript, Python과 같은 언어에서는 변수의 타입을 선언하지 않고 바로 변수를 사용할 수 있는데, 함수 정의에서도 마찬가지로 함수 타입을 선언하지 않고 사용할 수 있다. Python에서 함수를 정의할 때에도 우리는 함수의 리턴 타입을 선언하지 않고 def를 이용해 함수를 정의한다.
이외에 C, C++, Java 언어는 오로지 함수만 지원하며 프로시저는 리턴 타입이 void인 함수이다. Pascal, Ada는 함수와 프로시저를 구분하여 사용하며 프로시저는 procedure로 시작하고 함수는 functiond으로 시작한다. Modula-2는 함수와 프로시저를 모두 프로시저로 부르며, 여기서 함수는 리턴 값이 있는 프로시저이다.
함수의 매개변수 전달
함수를 호출하여 사용할 때에는 매개변수도 함께 사용한다. 함수 호출에서 사용된 매개변수는 실매개변수(actual parameter), 인자(argument)라고 불린다. 반대로 함수의 정의에서 사용된 매개변수는 형식매개변수(formal parameter)라 불린다. 함수의 호출이 발생하면 인자의 값을 매개변수에 전달하기 위해 인자와 매개변수를 서로 대응시켜야 하는데 이것을 매개변수 전달이라 부르며, 대표적으로 4가지 매개변수 전달 방식이 있다.
1) 값 전달(pass by value)
값 전달 방식은 가장 간단한 매개변수 전달 방식으로 거의 모든 언어가 기본적인 매개변수 전달 방식으로 제공한다. 말 그대로 함수를 호출할 때, 인자의 값을 계산해 대응되는 매개변수에 전달하는 것이다. 전달 과정은 다음과 같다. 수식인 인자 값을 계산하고, 계산된 값들을 대응되는 매개변수에 전달한다. 매개변수는 전달된 값으로 초기화되고, 함수 본체를 실행한다.
>>> int a = 10;
>>> int b = 5;
>>>print max(2 * a, a +b);
위 예를 살펴보자. 위 예시에서는 수식인 인자 값을 계산하면 20, 15가 될 것이고 이 값이 대으오디는 매개변수에 전달되어 함수 본체가 실행될 것이다. 이와 같이 값 전달 방식은 간단하며 이해하기도 쉽지만, 제한 사항이 있다. 다음 swap 함수를 살펴보자.
fun void swap(int x, int y)
let int temp = x; in
x = y;
y = temp;
end;
>>> int a = 10;
>>> int b = 5;
>>> swap(a, b);
이 코드가 실행되면 결과는 당연히 a와 b의 값이 서로 바뀌었을 것이라 생각한다. 하지만 실제로 swap 함수가 실행되면 매개변수 x와 y의 값은 서로 교환되지만, 함수 호출이 끝나면 변수 a와 b의 값은 그대로이다. 심지어 함수 호출 이후에는 변수 x와 y는 유효한 변수가 아니므로 사용도 불가능하다. 따라서 값 전달 방식을 사용해서는 swap 함수를 제대로 사용할 수 없다.
2) 참조 전달(pass by reference)
위 값 전달 방식의 문제점을 참조 전달 방법을 사용해 해결할 수 있다. 참조 전달 방식은 함수를 호출할 때, 인자에 대한 참조(reference)를 전달하는 방법이다. 참조는 사용할 때 마다 그 주소를 자동으로 따라가는 자동 주소 참조가 이루어진다는 면에서 포인터와는 조금 다르다. 참조 전달 방식의 작동 방식은 다음과 같다. 함수 호출 시 인자의 주소가 계산되어 매개변수에 전달된다. 따라서 인자는 할당된 기억 장소가 있는 변수여야 한다. 함수 내에서 매개변수를 사용하면 자동 주소 참조가 이루어져 대응되는 인자에 접근하므로, 매개변수 값을 변경하면 자동으로 대응되는 인자 값이 변경된다. 참조 전달을 이용하여 매개변수를 전달하면 인자와 대응되는 매개변수가 서로 이명이 되어 한 기억 장소에 대한 두 개의 다른 이름이 생긴다. 위 swap 함수를 참조 전달 방식을 이용해 C++ 언어로 작성해보자.
void swap(int &x, int &y)
{
int t = x;
x = y;
y = t;
}
이 함수를 이용하면 값 전달 방식에서는 제대로 작동하지 않았던 두 변수의 값 교환을 정상적으로 할 수 있다. 하지만 C++이 아닌 C에서는 값 전달 방식만 지원하는데, 그렇다면 C에서는 이 swap 함수를 절대 구현할 수 없을까? 그것은 아니다. 포인터를 이용하면 C에서도 참조 전달 방식의 효과를 내도록 구현할 수 있다.
참조 전달 방식은 문제가 없을까? 아쉽게도 앞서 참조 전달 방식에서는 이명이 발생한다고 하였다. 모두 같은 주소 공간을 가리키지만 이름이 다른 이명이 존재하면 프로그램의 의미를 쉽게 파악하기 어렵다. 즉, 참조 전달 방식은 인자를 수정할 수 있지만 이명이 발생해 프로그램의 의미 파악이 어렵다는 문제점이 있다.
3) 값 - 참조 전달(pass by value - result)
값 전달의 문제를 해결하기 위해 참조 전달을 사용하였지만 이명으로 인한 문제가 발생하였다. 이제 이명의 문제점을 해결하기 위한 값 - 참조 전달 방식을 알아보자. 기본 아이디어는 함수 호출, 리턴할 때 2번 매개변수 전달을 하는 것이다. 첫 번째 값 전달에서는 인자 값을 매개변수에 전달(복사), 두 번째 결과 전달에서는 매개변수 값을 인자에 역으로 전달(복사)한다. 값 전달은 copy - in, 결과 전달은 copy - out이라 한다.
4) 이름 전달(pass by name)
이름 전달은 상당히 특이한 매개변수 전달 방식인데, 이 방식의 핵심 개념은 지연 계산이다. 인자는 대응되는 매개변수에 대한 수식을 해당 변수가 사용될 때 까지 계산하지 않다가 실제로 매개변수가 사용될 때에 계산한다. 아래 예시를 통해 이해해보자.
int i; int a[10];
void p(int x)
{
i = i + 1;
x = x + 1;
}
main()
{
i = 1;
a[1] = 10;
a[2] = 20;
p(a[i]);
}
이름 전달의 방식에서는 매개변수를 실제 사용할 때에 계산한다고 하였다. 따라서 p(a[i]) 함수가 호출되면 i = i +1이 실행되어 i의 값이 2가 되고, x = x + 1을 계산하는데 이때 x는 매개변수인 a[i]인데 여기서 i 값은 앞서 2로 계산하였다. 따라서 x = x + 1은 a[2] = a[2] + 1이 되어 결과로 20 + 1 = 21의 값을 반환하게 된다.
여러 언어의 함수 매개변수 전달
Ada는 매개변수 전달 방법으로 값 전달, 값 - 결과 전달 방식을 사용한다. 값 전달 방식을 이용하려면 매개변수 앞에 in으로 선언하고, 값 - 결과 전달 방식에서는 매개변수 앞에 in - out 방식으로 선언한다. C는 앞에서 언급하였듯 값 전달 방식만 지원하지만, 포인터 변수를 이용해 참조 전달의 효과를 낼 수 있다. C++, Pascal, Modula - 2, Java는 값 전달과 참조 전달 방식을 지원하며, FORTRAN은 참조 전달 방식만을 제공한다.
'CS > 프로그래밍 언어론' 카테고리의 다른 글
[프로그래밍 언어론] 예외의 처리 (0) | 2025.07.07 |
---|---|
[프로그래밍 언어론] 함수의 타입 규칙 (0) | 2025.07.04 |
[프로그래밍 언어론] 타입 시스템 (0) | 2025.07.04 |
[프로그래밍 언어론] 타입과 언어의 분류 (0) | 2025.07.03 |
[프로그래밍 언어론] 자료형 (0) | 2025.07.03 |