함수 (컴퓨터 과학)
"오늘의AI위키"의 AI를 통해 더욱 풍부하고 폭넓은 지식 경험을 누리세요.
- 1. 개요
- 2. 역사
- 3. 기본 개념
- 4. 장점
- 5. 구현
- 5.1. 명명 (Naming)
- 5.2. 호출 구문 (Call syntax)
- 5.3. 매개변수 (Parameters)
- 5.4. 반환값 (Return value)
- 5.5. 부작용 (Side effects)
- 5.6. 지역 변수 (Local variables)
- 5.7. 중첩 호출 및 재귀 (Nested call - recursion)
- 5.8. 중첩 범위 (Nested scope)
- 5.9. 재진입성 (Reentrancy)
- 5.10. 오버로딩 (Overloading)
- 5.11. 클로저 (Closure)
- 5.12. 예외 보고 (Exception reporting)
- 5.13. 호출 오버헤드 (Call overhead)
- 5.14. 공유 (Sharing)
- 5.15. 상호 운용성 (Inter-operability)
- 5.16. 내장 함수 (Built-in functions)
- 6. 프로그래밍
- 7. 언어별 예제
- 8. 재귀 호출 (Recursive call)
- 참조
1. 개요
함수(컴퓨터 과학)는 특정 작업을 수행하는 코드 블록으로, 입력 값을 받아 처리하고 결과를 반환하거나, 값을 반환하지 않고 작업을 수행한다. 초기 컴퓨터에서는 함수 호출 명령어가 없었지만, 이후 하드웨어 스택을 이용하거나 호출 규약을 사용하여 함수를 구현했다. 함수는 구조적 프로그래밍, 코드 재사용, 캡슐화, 가독성 향상, 추적성 향상 등 다양한 장점을 제공하며, 프로그래밍 언어에 따라 서브루틴, 프로시저, 메서드 등 다양한 용어로 불린다. 함수는 값 호출, 참조 호출, 이름 호출 등 다양한 방식으로 인수를 전달받으며, 재귀 호출, 오버로딩, 클로저, 예외 처리 등 다양한 기능을 지원한다.
함수의 개념은 컴퓨팅 머신이 이미 존재한 이후 어느 시점에서 발전하였다. 초기 컴퓨터는 산술 및 조건 점프 명령은 있었지만, 프로시저 호출을 위한 특별한 명령들은 여러 해에 걸쳐 상당히 변화되었다. SSEM이나 RCA 1802와 같은 초기 컴퓨터들과 마이크로프로세서들은 단일 함수 호출 명령어가 없었다. 함수는 구현할 수 있었지만 프로그래머들이 호출 시퀀스(일련의 명령)를 각각의 호출 영역에 사용해야 했다.[54]
함수는 컴퓨터 과학에서 특정 작업을 수행하는 코드 블록으로, 컴퓨팅 머신 등장 이후 발전해왔다. 초기 컴퓨터에는 함수 호출을 위한 별도 명령어가 없었지만, PDP-11(1970년)처럼 스택 푸시형 함수 호출 명령을 갖춘 컴퓨터가 등장하면서 함수 중첩 및 반복적 사용이 가능해졌다.[54]
2. 역사
IBM 1620, 인텔 8008, PIC 마이크로컨트롤러와 같은 일부 초기 컴퓨터들과 마이크로프로세서들은 전용 하드웨어 스택을 사용하여 반환 주소를 저장하는 단일 명령 함수 호출을 갖고 있었다. 이러한 하드웨어는 함수를 몇 번만 겹쳐 사용하는 것은 지원했지만 반복적인 함수는 지원하지 못했다. 유니박 I, PDP-1, IBM 1130과 같은 1960년대 중순 이전의 머신들은 일반적으로 호출 규약을 사용하여 명령 카운터를 호출된 함수의 최초 메모리 지점에 저장하였다. 이는 함수를 여러 번 겹쳐 사용하는 것은 지원했지만, 반복적인 함수는 지원하지 못했다.[54]
PDP-11(1970년)은 스택 푸시형 함수 호출 명령을 갖춘 최초의 컴퓨터 중 하나이며, 이 기능은 임의로 겹쳐 함수를 사용하는 것과 반복적인 함수를 모두 지원한다.[54]
함수를 호출하는 단위에 대한 아이디어는 존 모클리와 캐슬린 안토넬리가 ENIAC 작업을 하는 동안 처음 고안되었으며, 1947년 1월 하버드 심포지엄에서 "EDVAC 유형 머신을 위한 문제 준비"에 기록되었다.[4] 모리스 윌크스, 데이비드 휠러, 스탠리 길은 '폐쇄된 서브루틴'이라는 개념을 발명했다.[5][6] 이는 '열린 서브루틴' 또는 매크로와 대조된다.[7] 앨런 튜링은 1945년 논문에서 서브루틴에 대해 논의했으며, 반환 주소 스택의 개념을 발명했다.[8]
서브루틴은 1945년 콘라트 추제의 Z4에서 구현되었다.[9][10]
케이 맥널티는 ENIAC 팀에서 존 모클리와 긴밀히 협력했으며, ENIAC 컴퓨터용 서브루틴에 대한 아이디어를 개발했다.[11] 그녀와 다른 ENIAC 프로그래머들은 서브루틴을 사용하여 미사일 궤도를 계산했다.[11]
골드스타인과 폰 노이만은 1948년 8월 16일에 서브루틴 사용에 대해 논의한 논문을 작성했다.[12]
2. 1. 언어별 지원
초기 어셈블러에서는 서브루틴 지원이 제한적이었다. 서브루틴은 서로 또는 주 프로그램과 명시적으로 분리되지 않았으며, 실제로 서브루틴의 소스 코드는 다른 서브 프로그램의 소스 코드와 섞여 있을 수 있었다. 일부 어셈블러는 호출 및 반환 시퀀스를 생성하기 위해 미리 정의된 매크로를 제공했다. 1960년대에 이르러 어셈블러는 인라인 및 별도로 어셈블된 서브루틴을 모두 지원하여 함께 연결할 수 있는 훨씬 더 정교한 지원 기능을 갖추게 되었다.[15]
사용자가 작성한 서브루틴과 함수를 지원하는 최초의 프로그래밍 언어 중 하나는 FORTRAN II였다. IBM FORTRAN II 컴파일러는 1958년에 출시되었다. ALGOL 58 및 기타 초기 프로그래밍 언어도 절차적 프로그래밍을 지원했다.[15]
2. 2. 라이브러리
서브루틴은 여러 프로그램에서 동일한 코드를 재사용할 수 있게 해 주었다. 초기 컴퓨터에서는 메모리가 매우 부족했기 때문에, 서브루틴은 프로그램 크기를 크게 줄이는 데 기여했다.[16]
많은 초기 컴퓨터가 천공 카드에서 프로그램 명령어를 메모리에 로드했다. 각 서브루틴은 별도의 테이프 조각이나 카드 덱으로 제공될 수 있었고, 메인 프로그램 전후에 로드되거나 연결될 수 있었다. 그리고 동일한 서브루틴 테이프나 카드 덱을 여러 다른 프로그램에서 사용할 수 있었다. ''서브루틴 라이브러리''라는 이름은 원래 공동 사용을 위해 테이프 또는 카드 덱의 색인화된 모음을 보관하는 라이브러리를 의미했다.
3. 기본 개념
함수는 프로그래밍 언어에서 반복되는 작업을 모듈로 묶은 것으로, 호출하는 쪽(메인 루틴)과 호출되는 함수(서브루틴)로 구분된다. 반복되는 코드 작성을 줄여 가독성과 유지 보수성을 높이고, 의미 있는 묶음을 표현하는 데 사용된다.
C++, 자바 등은 오버로딩 함수를 지원하여, 반환형과 함수 이름이 같아도 인자 목록에 따라 다른 함수를 호출할 수 있다. 비주얼 베이직은 Sub와 Function으로 함수를 구분하며, C와 C++는 반환형은 일치해야 하고 반환값이 없는 함수는 void로 선언한다.
3. 1. 호출 및 입출력
함수를 지원하는 일반적인 프로그래밍 언어에서는 다음과 같은 구조를 갖는다.
일부 프로그래밍 언어에서는 함수와 서브루틴을 구분하기도 한다. 파스칼이나 포트란과 같은 언어는 반환값이 없는 경우를 서브루틴, 반환값이 있는 경우를 함수로 부른다. 반면 C 등의 언어에서는 함수와 서브루틴이 동의어이다.
서브루틴이 결과로 값을 반환하는 경우, 해당 값은 반환값이라고 한다.[31]
프로그래밍 언어에 따라 서브루틴을 "결과로 값을 반환하는 것"과 "처리만 수행하고 값을 반환하지 않는 것"으로 분류하고 구분하는 경우가 있다. 이 구분은 프로그래밍 언어의 사양으로 정해지므로 언어에 따라 구분이나 명칭이 다르다. 예를 들어, 파스칼에서는 반환값이 있는 것을 함수라고 부르고, 반환값이 없는 것을 프로시저라고 부른다. C 언어에서는 모두 함수라고 부른다. ALGOL에서는 모두 프로시저라고 부른다.
또한, 서브루틴은 호출 시 입력으로 '''인수'''를 받아서, 서브루틴 내에서 선언된 "가인수"라고 불리는 변수를 통해 내부 처리에 사용할 수 있다.
일반적으로는 반환값에 의해 단일 결과를 출력으로 반환하지만, 참조 호출을 사용함으로써 복수의 결과를 출력으로 반환하거나, 전달 시의 데이터 복사의 비용을 줄일 수도 있게 된다.
인수 전달 방식은 다음과 같다.[33]
규칙 | 설명 | 사용 언어 |
---|---|---|
값 호출 | 인수의 복사본을 전달한다. | 알골 60 이후 대부분의 알골 계열 언어 (Pascal, Delphi, Simula, CPL, PL/M, Modula, Oberon, Ada 등) 및 C, C++, Java 등 다수 |
참조 호출 | 인수에 대한 참조(일반적으로 주소)를 전달한다. | 알골 60 이후 대부분의 알골 계열 언어 (Algol 68, Pascal, Delphi, Simula, CPL, PL/M, Modula, Oberon, Ada 등) 및 C++, Fortran, PL/I 등에서 선택 가능 |
결과 호출 | 호출 중에 계산된 값을 반환 시 인수로 복사한다. | Ada OUT 매개변수 |
값-결과 호출 | 인수의 복사본을 전달하고, 호출 중에 계산된 값을 반환 시 인수로 복사한다. | Algol, Swift in-out 매개변수 |
이름 호출 | 매크로와 유사하게 매개변수를 평가되지 않은 인수 표현식으로 대체한 다음, 호출될 때마다 호출자의 컨텍스트에서 인수를 평가한다. | Algol, Scala |
상수 값 호출 | 값 호출과 유사하지만, 매개변수를 상수로 취급한다. | PL/I NONASSIGNABLE 매개변수, Ada IN 매개변수 |
3. 1. 1. 값에 의한 호출 (Call by Value)
값에 의한 호출은 인자를 평가한 다음 그 값의 사본을 함수에 전달하는 방식이다.[33] 값 호출로 변수를 전달할 때는 메인 루틴과는 별도로 서브루틴 측에서 변수를 위한 기억 영역을 확보하고, 거기에 일단 값(데이터)을 복사한다. 따라서 서브루틴에서 변수 값을 변경하더라도 메인 루틴으로 돌아왔을 때는 원래 값이 그대로 유지된다.3. 1. 2. 참조에 의한 호출 (Call by Reference)
인자에 대한 참조(주소)를 전달하는 방식이다.[33] 참조 호출로 변수를 전달하면 메인 루틴과 서브루틴에서 변수의 기억 영역을 공유한다. 따라서 서브루틴 내에서 해당 변수에 대해 행한 변경은 메인 루틴으로 돌아와도 그대로 반영된다.[33] 일반적으로 참조 호출을 사용하면 복수의 결과를 출력으로 반환하거나, 전달 시 데이터 복사 비용을 줄일 수 있다.3. 1. 3. 결과에 의한 호출 (Call by Result)
함수 호출 시 매개변수 값은 함수로부터 반환될 때 인자에 복사된다.[33]3. 1. 4. 값 결과에 의한 호출 (Call by Value-Result)
값 결과에 의한 호출은 함수의 진입점에 매개변수 값이 복사되며, 함수 반환 시에도 값이 복사되는 방식이다.[33]3. 1. 5. 이름에 의한 호출 (Call by Name)
이름에 의한 호출은 매크로처럼 매개변수를 미평가된 인자식으로 대체한다.[33]3. 1. 6. 상수값에 의한 호출 (Call by Constant Value)
매개변수가 상수로 취급되는 경우를 제외하고 값에 의한 호출과 비슷하다.[33]3. 2. 구조
함수를 지원하는 일반적인 프로그래밍 언어에서는 다음과 같은 구조를 갖는다.- 어떤 루틴에서 함수를 호출한다. 이때 함수가 가지는 특정 변수에 값을 전달하기도 하며, 이 특정 변수를 매개변수라고 하며 전달되는 값을 인자(argument)라고 부른다.
- 함수가 호출되어 계산을 수행한다.
- 함수가 종료되고 실행 흐름이 원래의 루틴으로 돌아온다. 경우에 따라서는 함수가 계산하여 반환된 값(반환값, return value)을 원래의 루틴에서 사용하기도 한다.
일부 프로그래밍 언어에서는 함수와 서브루틴을 구분하기도 한다. 파스칼이나 포트란과 같은 언어는 반환값이 없는 경우를 서브루틴, 반환값이 있는 경우를 함수로 부른다. 반면 C 등의 언어에서는 함수와 서브루틴이 동의어이다.
함수가 결과값을 반환하는 것 이외에 외부에 영향을 주는 작동을 할 경우 부작용이 있다고 부른다. 부작용이 없는 함수를 순수한 함수로 부르며, 하스켈과 같은 순수 함수형 프로그래밍에서는 모든 함수가 순수한 함수이다.
일반적으로, 호출 가능한 단위는 첫 번째 명령부터 시작하여 내부 논리에 따라 순차적으로 실행되는 일련의 명령어 집합이다. 프로그램 실행 중에 여러 번 호출(call)될 수 있다. 실행은 호출 명령 다음의 명령으로 계속되며, 호출된 단위가 제어를 반환할 때까지 지속된다.
함수의 호출 규약은 다음과 같다.
규약 | 설명 |
---|---|
값에 의한 호출 | 인자를 평가한 다음 값의 사본을 함수에 전달한다. |
참조에 의한 호출 | 인자에 대한 참조로서, 보통 이에 대한 주소가 전달된다. |
결과에 의한 호출 | 매개변수 값은 함수로부터 반환 시 인자에 복사된다. |
값 결과에 의한 호출 | 매개변수 값은 함수의 진입점에 복사되며 반환 시에도 그렇게 처리된다. |
이름에 의한 호출 | 매크로처럼 매개변수를 미평가된 인자식으로 대체한다. |
상수값에 의한 호출 | 매개변수가 상수로 취급되는 경우를 제외하고 값에 의한 호출과 비슷하다. |
4. 장점
함수를 사용하면 다음과 같은 장점이 있다.
- 큰 프로그램을 여러 부분으로 나누어 구조적 프로그래밍을 할 수 있다.
- 같은 코드를 반복해서 작성하지 않아도 되므로 프로그램의 용량을 줄일 수 있다. 또한, 다른 부분이나 다른 프로그램에서 같은 코드를 재사용할 수 있다.[16]
- 함수의 기능과 내부 구현을 분리하는 캡슐화가 이루어진다.
- 복잡한 프로그래밍 작업을 더 간단한 단계로 분해할 수 있다.
- 프로그램 내에서 중복 코드를 줄일 수 있다.
- 대규모 프로그래밍 작업을 여러 프로그래머 또는 프로젝트의 여러 단계로 분할할 수 있다.
- 함수 사용자가 구현 세부 사항을 알 필요가 없도록 숨길 수 있다.
- 코드 블록을 설명적인 함수 이름으로 대체하여 코드의 가독성을 높일 수 있다.
- 추적성을 향상시킨다. 즉, 대부분의 언어는 관련된 함수 이름과 파일 이름 및 줄 번호와 같은 더 많은 정보를 포함하는 호출 추적을 얻는 방법을 제공한다. 코드를 함수로 분해하지 않으면 디버깅이 어려워진다.
- 반복되는 처리를 서브루틴화하여 가독성과 유지보수성을 높게 유지할 수 있다.
5. 구현
대부분의 현대 프로그래밍 언어는 함수를 정의하고 호출하는 기능을 제공한다. 함수는 다음과 같은 특징을 갖는다.
- 프로그램의 나머지 부분과 구분되는 구현을 갖는다.
- 식별자 (이름)를 갖는다.
- 형식 매개변수에 이름과 자료형을 정의한다.
- 반환 값이 있는 경우, 반환 값에 자료형을 할당하고, 함수 본문에서 반환 값을 지정한다.
- 함수를 호출하고, 호출된 함수의 형식 매개변수에 해당하는 실제 매개변수를 제공한다.
- 반환 제어를 호출 지점으로 반환하고, 호출에서 반환된 값을 사용하거나 폐기한다.
- 변수에 대한 개인적인 명명 범위를 제공하고, 함수 외부에서 접근 가능한 변수를 식별한다.
- 예외 조건을 함수 밖으로 전달하고 호출 컨텍스트에서 처리한다.
- 함수를 모듈, 라이브러리, 객체 또는 클래스와 같은 컨테이너로 패키징한다.
함수를 지원하는 일반적인 프로그래밍 언어에서는 다음과 같은 구조를 갖는다.[4]
- 어떤 루틴에서 함수를 호출하고, 매개변수에 값을 전달한다.
- 함수가 호출되어 계산을 수행한다.
- 함수가 종료되고 실행 흐름이 원래의 루틴으로 돌아오며, 경우에 따라 반환값을 사용한다.
일부 프로그래밍 언어에서는 함수와 서브루틴을 구분하기도 한다. 파스칼이나 포트란과 같은 언어는 반환값이 없는 경우를 서브루틴, 반환값이 있는 경우를 함수로 부른다. 반면 C 등의 언어에서는 함수와 서브루틴이 동의어이다.
함수가 결과값을 반환하는 것 외에 외부에 영향을 주는 작동을 할 경우 부작용이 있다고 부른다. 부작용이 없는 함수를 순수한 함수라고 하며, 하스켈과 같은 순수 함수형 프로그래밍에서는 모든 함수가 순수한 함수이다.
함수 호출은 존 모클리와 캐슬린 안토넬리가 ENIAC 작업을 하는 동안 처음 고안했으며,[4] 모리스 윌크스, 데이비드 휠러, 스탠리 길이 '폐쇄된 서브루틴'이라고 부르며 공식화했다.[5][6] 앨런 튜링은 반환 주소 스택의 개념을 발명하기도 했다.[8]
초기 컴퓨터와 마이크로프로세서에는 단일 서브루틴 호출 명령이 없었지만, Z4에서 구현되었다. 앨런 M. 튜링은 "bury"와 "unbury"라는 용어를 사용했고,[9][10] 존 모클리는 서브루틴을 제안했다.[4] 케이 맥널티는 ENIAC 컴퓨터용 서브루틴 아이디어를 개발했다.[11] 골드스타인과 폰 노이만은 서브루틴 사용에 대해 논의했다.[12]
IBM 1620, 인텔 4004 및 인텔 8008, PIC 마이크로컨트롤러와 같은 일부 초기 컴퓨터와 마이크로프로세서는 전용 하드웨어 스택을 사용했다. 1960년대 중반 이전의 기계는 호출 규칙을 사용했다. IBM System/360은 서브루틴 호출 명령을 가지고 있었다. B5000[13]은 서브루틴 반환 데이터를 스택에 저장하는 최초의 컴퓨터 중 하나이다. PDP-6[14], PDP-10, PDP-11, VAX-11은 서브루틴 호출 명령을 가졌다.[15]
초창기 어셈블러에서는 서브루틴 지원이 제한적이었지만, 1960년대에는 정교한 지원 기능을 갖추게 되었다. FORTRAN II, ALGOL 58 등 초기 프로그래밍 언어도 절차적 프로그래밍을 지원했다.
대부분의 현대적인 함수 호출 구현은 콜 스택을 사용하는데, 각 프로시저 호출은 스택 프레임을 생성한다. 콜 스택은 메모리의 연속된 영역으로 구현되며, 스택의 맨 아래 주소는 설계에 따라 다를 수 있다. 일부 설계에서는 별도 스택을 사용하기도 했다.
스택 기반 프로시저 호출은 메모리를 절약하고 재귀 함수 호출을 허용한다. 다중 스레드 환경에서는 둘 이상의 스택이 있을 수 있으며, 코루틴이나 지연 평가를 지원하는 환경에서는 다른 데이터 구조를 사용할 수도 있다.
호출 가능 단위는 일반적으로 첫 번째 명령부터 시작하여 내부 논리에 따라 순차적으로 실행되며, 프로그램 실행 중에 여러 번 호출될 수 있다. 호출된 단위가 제어를 반환하면 실행은 호출 명령 다음으로 계속된다.
5. 1. 명명 (Naming)
일부 프로그래밍 언어에서는 값을 반환하는 함수와 그렇지 않은 함수를 구분하여 부른다. 파스칼, 포트란, 에이다와 같은 언어에서는 반환값이 없는 경우를 서브루틴 또는 프로시저, 반환값이 있는 경우를 함수라고 부른다.[27] 반면 C, C++, C#, Lisp와 같은 언어에서는 함수라는 하나의 이름만 사용하며, C 계열 언어에서는 반환 값이 없음을 나타내기 위해 `void` 키워드를 사용한다.[27]VB 계열 언어에서는 호출 가능한 단위를 '프로시저'라고 부르며, `Sub` 키워드는 값을 반환하지 않는 프로시저를, `Function` 키워드는 값을 반환하는 프로시저를 나타낸다. 클래스 내에서 사용되는 프로시저는 메서드라고 불린다.[27]
일본 정보 처리 추진 기구(IPA)에서 운영하는 기본 정보 기술자 시험에서 사용되는 의사 언어에서는 서브루틴을 반환값 유무에 따라 "함수"와 "절차"로 분류하는데, 이는 파스칼의 관습과 유사하다.[31]
FORTRAN에서는 값을 반환하는 서브 프로그램을 함수( `function` ), 값을 반환하지 않는 서브 프로그램을 서브루틴( `subroutine` )이라고 부른다.[41]
커니핸과 리치의 저서 『프로그래밍 언어 C』에서는 C 언어의 함수가 파스칼의 "절차"나 "함수"에 해당한다고 설명한다.[41] C 언어에서는 반환값 유무에 관계없이 "함수"(function)라고 부르며, 값을 반환하지 않는 함수는 `void` 형을 사용한다.[42]
5. 2. 호출 구문 (Call syntax)
파스칼, 포트란, 에이다와 같은 일부 언어는 값을 반환하는 호출 가능 단위(''함수'' 또는 ''서브 프로그램'')와 그렇지 않은 단위(''서브루틴'' 또는 ''프로시저'')를 구분한다.[31] C, C++, C#, Lisp와 같은 다른 언어는 호출 가능한 단위에 대해 ''함수''라는 하나의 이름만 사용한다. C 계열 언어는 반환 값이 없음을 나타내기 위해 `void` 키워드를 사용한다.값을 반환하도록 선언된 경우, 호출은 반환 값을 사용하기 위해 식에 포함될 수 있다. 예를 들어, 제곱근 호출 가능 단위는 `y = sqrt(x)`와 같이 호출될 수 있다.
값을 반환하지 않는 호출 가능 단위는 `print("hello")`와 같이 독립형 문으로 호출된다. 이 구문은 값을 반환하는 호출 가능 단위에도 사용할 수 있지만 반환 값은 무시된다.
일부 구형 언어에서는 `CALL print("hello")`와 같이 반환 값을 사용하지 않는 호출에 키워드가 필요하다.
5. 3. 매개변수 (Parameters)
함수는 매개변수를 통해 입력을 받는다. 인수는 호출 시 함수에 전달되는 실제 값이다.[33] 인수를 전달하는 방법에는 값 호출, 참조 호출 등이 있다.[33]규칙 | 설명 | 사용 언어 |
---|---|---|
값 호출 | 인수의 복사본을 전달한다. | 알골 60 이후 대부분의 알골 계열 언어 (Pascal, Delphi, Simula, CPL, PL/M, Modula, Oberon, Ada 등) 및 C, C++, Java 등 다수 |
참조 호출 | 인수에 대한 참조를 전달한다. 일반적으로 주소. | 알골 60 이후 대부분의 알골 계열 언어 (Algol 68, Pascal, Delphi, Simula, CPL, PL/M, Modula, Oberon, Ada 등) 및 C++, Fortran, PL/I 등에서 선택 가능 |
결과 호출 | 호출 중에 계산된 값을 반환 시 인수로 복사한다. | Ada OUT 매개변수 |
값-결과 호출 | 인수의 복사본을 전달하고, 호출 중에 계산된 값을 반환 시 인수로 복사한다. | Algol, Swift in-out 매개변수 |
이름 호출 | 매크로와 유사 – 매개변수를 평가되지 않은 인수 표현식으로 대체한 다음, 호출 가능 개체가 매개변수를 사용할 때마다 호출자의 컨텍스트에서 인수를 평가한다. | Algol, Scala |
상수 값 호출 | 값 호출과 유사하지만, 매개변수를 상수로 취급한다. | PL/I NONASSIGNABLE 매개변수, Ada IN 매개변수 |
값 호출은 실제 인수를 전달할 때 값만 전달하고, 기억 위치(주소)는 전달하지 않는다.[33] 변수를 값 호출로 전달하면, 메인 루틴과는 별도로 서브루틴 측에서 변수의 기억 영역을 확보하고, 거기에 값을 복사한다. 따라서 서브루틴 측에서 변수 값을 변경해도 메인 루틴에서는 원래 값 그대로 유지된다.[33]
참조 호출은 실제 인수를 전달할 때 기억 위치(주소)를 전달한다.[33] 변수를 참조 호출로 전달하면, 메인 루틴과 서브루틴에서 변수의 기억 영역을 공유한다. 따라서 서브루틴 내에서 해당 변수에 대해 행한 변경은 메인 루틴에도 그대로 반영된다.[33]
일반적으로 반환값으로 단일 결과를 출력하지만, 참조 호출을 사용하면 복수의 결과를 출력하거나, 전달 시 데이터 복사 비용을 줄일 수 있다.[33]
5. 4. 반환값 (Return value)
함수는 반환값을 통해 호출자(함수를 호출한 쪽)에게 결과를 돌려준다. 파스칼이나 포트란 같은 일부 프로그래밍 언어는 반환값이 없는 경우를 서브루틴, 반환값이 있는 경우를 함수로 구분한다.[31] 반면, C 등의 언어에서는 함수와 서브루틴을 동일하게 취급한다.프로그래밍 언어에 따라 서브루틴을 "값을 반환하는 것"과 "값을 반환하지 않는 것"으로 분류한다. 예를 들어, 파스칼에서는 반환값이 있는 것을 함수, 없는 것을 프로시저라고 부른다.[31] C 언어에서는 모두 함수라고 부르며, 값을 반환하지 않는 함수는 `void` 형으로 선언한다.
일본의 정보 처리 추진 기구(IPA)가 운영하는 기본 정보 기술자 시험에서 사용되는 의사 언어에서는, 서브루틴을 반환값 유무에 따라 "함수"와 "절차"로 분류한다. 반환값이 있는 쪽이 함수이고, 반환값이 없는 쪽이 절차이다.[31]
서브루틴은 호출 시 입력으로 인수를 받아, 서브루틴 내에서 선언된 "가인수" 변수를 통해 내부 처리에 사용한다. 인수를 전달하는 방법에는 값 호출과 참조 호출이 있다.[33]
- 값 호출: 실제 인수의 값만 전달하고, 기억 위치(주소)는 전달하지 않는다. 서브루틴에서 변수 값을 변경해도 원래 값은 유지된다.
- 참조 호출: 실제 인수의 기억 위치(주소)를 전달한다. 서브루틴에서 변수 값을 변경하면 원래 값도 변경된다.
일반적으로 반환값은 단일 결과를 출력하지만, 참조 호출을 사용하면 복수 결과를 반환하거나 데이터 복사 비용을 줄일 수 있다.
펄에서는 사용자가 정의한 서브루틴에서 특수 변수를 통해 인수에 접근하고, `return` 문[43]을 사용하여 값을 반환한다. Visual Basic 계열 언어에서는 값을 반환하는 프로시저를 "Function 프로시저", 값을 반환하지 않는 프로시저를 "Sub 프로시저"라고 부른다.
5. 5. 부작용 (Side effects)
함수가 결과값을 반환하는 것 외에 외부에 영향을 주는 작동을 할 경우 부작용이 있다고 부른다.[21] 부작용은 함수 처리 실행에 의해 시스템에 변화를 발생시킨다. 많은 상황에서 호출 가능 객체는 전달되거나 전역으로 사용되는 데이터를 수정하거나, 주변 장치에서 읽거나 쓰는 동작, 파일에 접근하거나, 프로그램 또는 시스템을 중단시키거나, 프로그램 실행을 일시 중지하는 등의 부작용을 가질 수 있다.부작용이 없는 함수를 순수한 함수라고 부른다. 하스켈과 같은 엄격한 함수형 프로그래밍 언어에서는 함수가 부작용을 가질 수 없으며, 이는 프로그램의 상태를 변경할 수 없음을 의미한다. 이러한 함수는 동일한 입력에 대해 항상 동일한 결과를 반환한다. 함수형 프로그래밍 언어는 일반적으로 반환 값도 부작용도 없는 함수는 가치가 없기 때문에 값을 반환하는 함수만 지원한다.
5. 6. 지역 변수 (Local variables)
대부분의 프로그래밍 언어는 중간 값을 저장하기 위해 함수가 소유한 메모리인 지역 변수를 지원한다. 이러한 변수는 일반적으로 호출 스택의 호출 활성 레코드에 반환 주소와 같은 다른 정보와 함께 저장된다.[21]5. 7. 중첩 호출 및 재귀 (Nested call - recursion)
함수는 자신을 호출할 수 있는데, 이를 재귀 호출이라고 한다.[47][31] 재귀 호출은 복잡한 알고리즘을 단순화하고, 어떤 처리를 통해 얻은 값에 대해 동일한 처리를 반복해야 하는 문제를 해결하는 데 유용하다.[48] 예를 들어 팩토리얼 계산,[31] 파일 시스템 같은 트리 구조 탐색,[49] 오셀로, 체스, 장기, 바둑 소프트웨어의 "미리 보기" 등에 사용된다.[50][51][52][53]ALGOL, PL/I, C를 거쳐 최신 언어들은 대부분 콜 스택을 사용하여 재귀 호출을 지원한다. 각 호출마다 새로운 지역 변수 복사본을 제공하여 중첩된 호출이 다른 호출의 변수에 영향을 주지 않도록 한다.
다음은 피보나치 수열을 찾는 C/C++ 재귀 함수의 예시이다.
int Fib(int n) {
if (n <= 1) {
return n;
}
return Fib(n - 1) + Fib(n - 2);
}
포트란 같은 초기 언어는 각 호출에 하나의 변수 세트와 반환 주소만 할당했기 때문에 처음에는 재귀를 지원하지 않았다.[22] 초기 컴퓨터 명령어 세트는 스택에 반환 주소와 변수를 저장하기 어려웠다. 인덱스 레지스터나 범용 레지스터가 있는 기계(예: CDC 6000 시리즈, PDP-6, GE 635, System/360, UNIVAC 1100 시리즈)는 레지스터 중 하나를 스택 포인터로 사용할 수 있었다.
일반적인 컴퓨터 및 프로그래밍 언어에서 서브루틴 호출 시 콜 스택에 인자와 지역 변수 등의 영역이 확보된다. 그러나 개인용 컴퓨터와 같이 자원이 넉넉한 환경에서도 콜 스택 용량은 기본적으로 스레드마다 수 MiB 정도로 설정되어 있다. 따라서 서브루틴 호출 계층이 너무 깊어지면 스택 오버플로우가 발생할 수 있다. 특히 재귀 호출은 호출 계층이 깊어지기 쉬워 재귀 횟수가 많으면 스택 오버플로우를 일으키기 쉽다.
5. 8. 중첩 범위 (Nested scope)
Ada, 파스칼, PL/I, 파이썬과 같은 일부 프로그래밍 언어는 함수 내부에 함수를 선언하고 정의하는 것을 지원한다. 이렇게 하면 내부 함수의 이름은 외부 함수의 본문 내에서만 볼 수 있다.[1]5. 9. 재진입성 (Reentrancy)
만약 호출 가능한 객체가 동일한 호출 가능 객체의 다른 실행이 이미 진행 중일 때에도 제대로 실행될 수 있다면, 해당 호출 가능 객체는 ''재진입 가능''이라고 한다. 재진입 가능 호출 가능 객체는 여러 스레드가 서로 간섭할 걱정 없이 동일한 호출 가능 객체를 호출할 수 있기 때문에 멀티 스레드 상황에서도 유용하다. IBM CICS 트랜잭션 처리 시스템에서, ''준재진입 가능''은 여러 스레드에서 공유되는 응용 프로그램에 대한 약간 덜 제한적인, 유사한 요구 사항이었다.5. 10. 오버로딩 (Overloading)
C++, 자바 등의 프로그래밍 언어는 함수 오버로딩을 지원한다. 오버로딩은 반환형과 함수 이름은 같지만 인자 목록의 개수나 자료형이 다른 여러 함수를 정의할 수 있게 해준다. 컴파일러는 호출 시 사용된 인자 목록에 따라 적절한 함수를 선택하여 실행한다.[21]오버로딩을 사용하면, 예를 들어 제곱근 함수를 실수, 복소수, 행렬 등 다양한 입력 유형에 대해 각각 다른 알고리즘으로 구현하면서도, 모두 'sqrt'라는 동일한 이름을 사용할 수 있다. 이는 'sqrt_real', 'sqrt_complex', 'sqrt_matrix'와 같이 복잡한 이름을 사용하는 것보다 코드를 더 쉽게 작성하고 유지 관리할 수 있게 해준다.[21]
다음은 C++에서 오버로딩을 사용하는 예시이다. `Area`라는 이름의 함수가 두 개 정의되어 있는데, 하나는 직사각형의 면적을 계산하고 다른 하나는 원의 면적을 계산한다.
// 높이와 너비로 정의된 직사각형의 면적을 반환합니다.
double Area(double h, double w) { return h * w; }
// 반지름으로 정의된 원의 면적을 반환합니다.
double Area(double r) { return r * r * 3.14; }
int main() {
double rectangle_area = Area(3, 4);
double circle_area = Area(5);
}
5. 11. 클로저 (Closure)
'''클로저'''는 호출 가능한 객체와, 생성된 환경에서 캡처된 일부 변수 값의 조합이다. 존 매카시가 도입한 Lisp 프로그래밍 언어의 주목할 만한 기능이었다. 구현 방식에 따라 클로저는 부작용을 위한 메커니즘 역할을 할 수 있다.5. 12. 예외 보고 (Exception reporting)
함수는 정상적인 상황 외에도, 호출자가 실행 중에 발생한 예외적 상황에 대해 알릴 필요가 있을 수 있다.[1]대부분의 현대 언어는 예외를 지원하며, 예외 처리기가 해당 조건을 처리할 때까지 호출 스택을 해제하는 예외적 제어 흐름을 허용한다.[1]
예외를 지원하지 않는 언어는 반환 값을 사용하여 호출의 성공 또는 실패를 나타낼 수 있다.[1] 또 다른 방법은 성공 표시를 위해 전역 변수와 같은 잘 알려진 위치를 사용하는 것이다.[1] 호출 가능 개체는 값을 쓰고 호출자는 호출 후 이를 읽는다.[1]
IBM System/360에서 서브루틴에서 반환 코드가 예상되었을 때, 반환 값은 종종 4의 배수로 설계되었다.[1] 이는 추가적인 조건 테스트를 피하기 위해 호출 명령 바로 뒤에 위치한 분기 테이블로 직접 분기 테이블 인덱스로 사용할 수 있었기 때문이다.[1] 이는 효율성을 더욱 향상시켰다.[1] System/360 어셈블리 언어에서, 예를 들어 다음과 같이 작성할 수 있다.[1]
```text
BAL 14, SUBRTN01 서브루틴으로 이동하여 반환 주소를 R14에 저장
B TABLE(15) reg 15의 반환 값을 사용하여 분기 테이블을 인덱싱하여,
- 적절한 분기 명령으로 분기
TABLE B OK 반환 코드 =00 정상 }
B BAD 반환 코드 =04 잘못된 입력 } 분기 테이블
B ERROR 반환 코드 =08 예상치 못한 조건 }
```
호출 가능 개체는 프로그램 내에서 정의되거나 여러 프로그램에서 사용할 수 있는 라이브러리에서 별도로 정의될 수 있다.[1]
5. 13. 호출 오버헤드 (Call overhead)
함수를 호출할 때는 몇 가지 추가적인 연산이 필요하므로 실행 시간이 더 걸릴 수 있다. 이러한 추가 연산을 호출 오버헤드라고 한다.대부분의 현대적인 프로그래밍 언어는 함수를 정의하고 호출하는 기능을 제공하며, 함수 호출과 반환을 구현하기 위해 콜 스택을 사용한다. 콜 스택은 각 함수 호출에 대한 정보를 저장하는 메모리 영역이다. 함수가 호출될 때마다 새로운 스택 프레임이 콜 스택에 추가되고, 함수가 반환될 때 해당 스택 프레임이 제거된다.
호출 시퀀스는 일반적인 명령어로 구현될 수도 있고, 특수 명령어를 사용할 수도 있다. 콜 스택은 메모리의 연속된 영역에 구현되며, 스택의 맨 아래가 낮은 주소인지 높은 주소인지는 설계에 따라 다르다.
스택 기반 프로시저 호출은 메모리를 절약하고 재귀 함수 호출을 가능하게 하는 장점이 있다. 다중 스레드 환경에서는 둘 이상의 스택이 있을 수 있으며, 코루틴이나 지연 평가를 지원하는 환경에서는 스택이 아닌 다른 데이터 구조를 사용할 수도 있다.
함수 호출에는 다음과 같은 실행 시 오버헤드가 발생할 수 있다.
- 호출 스택 저장 공간 할당 및 회수
- 프로세서 레지스터 저장 및 복원
- 입력 변수 복사
- 호출 후 호출자의 컨텍스트에 값 복사
- 반환 코드의 자동 테스트
- 예외 처리
- 객체 지향 언어의 가상 메서드와 같은 디스패칭
컴파일러는 호출 및 반환 문을 호출 규약에 따라 기계어로 변환한다. 동일하거나 호환되는 컴파일러로 컴파일된 코드의 경우, 함수는 이를 호출하는 프로그램과 별도로 컴파일될 수 있다. 호출 및 반환 문에 해당하는 일련의 명령어는 프로시저의 프롤로그와 에필로그라고 불린다.
5. 13. 1. 컴파일러 최적화 (Compiler optimization)
컴파일러는 호출 오버헤드를 줄이기 위해 다양한 최적화를 수행한다. 호출에는 실행 시 오버헤드가 있는데, 여기에는 다음이 포함될 수 있다.- 호출 스택 저장 공간 할당 및 회수
- 프로세서 레지스터 저장 및 복원
- 입력 변수 복사
- 호출 후 호출자의 컨텍스트에 값 복사
- 반환 코드의 자동 테스트
- 예외 처리
- 객체 지향 언어의 가상 메서드와 같은 디스패칭
호출의 런타임 비용을 최소화하기 위해 다양한 기술이 사용된다. 호출 오버헤드를 최소화하기 위한 일부 최적화는 간단해 보일 수 있지만, 호출 가능 항목에 부작용이 있는 경우에는 사용할 수 없다. 예를 들어, 표현식 `(f(x)-1)/(f(x)+1)`에서 함수 `f`는 두 번 호출 시 서로 다른 결과를 반환할 수 있으므로, 값을 두 번 사용한다고 해서 한 번만 호출할 수 없다. 또한, 나눗셈 연산자의 피연산자 평가 순서를 정의하는 몇 안 되는 언어에서는, 첫 번째 호출이 `x`의 값을 변경했을 수 있으므로 두 번째 호출 전에 `x`의 값을 다시 가져와야 한다. 호출 가능 항목에 부작용이 있는지 여부를 판단하는 것은 어려우며, 실제로 라이스의 정리에 의해 결정 불가능하다. 따라서 이 최적화는 순수 함수형 프로그래밍 언어에서는 안전하지만, 함수형 언어로 제한되지 않은 언어의 컴파일러는 일반적으로 최악의 경우를 가정하여 모든 호출 가능 항목에 부작용이 있을 수 있다고 간주한다.[23]
5. 13. 2. 인라인 확장 (Inlining)
인라인 확장은 특정 호출 가능 객체에 대한 호출을 제거하는 방법이다. 컴파일러는 각 호출을 호출 가능 객체의 컴파일된 코드로 대체한다. 이렇게 하면 호출 오버헤드를 피할 수 있을 뿐만 아니라, 해당 호출의 컨텍스트와 인수를 고려하여 호출자 코드를 더 효과적으로 최적화할 수 있다. 그러나 인라인 확장은 일반적으로 컴파일된 코드 크기를 증가시키지만, 단 한 번만 호출되거나 본문이 한 줄과 같이 매우 짧은 경우에는 예외이다.[1]5. 14. 공유 (Sharing)
함수를 호출할 수 있는 단위에 대한 개념은 존 모클리와 캐슬린 안토넬리가 ENIAC 작업을 진행하면서 처음 고안했으며, 1947년 1월 하버드 심포지엄에서 "EDVAC 유형 머신을 위한 문제 준비"에 기록되었다.[4] 이 개념을 공식적으로 발명한 사람으로는 모리스 윌크스, 데이비드 휠러, 스탠리 길이 있으며, 이들은 '폐쇄된 서브루틴'이라고 불렀다.[5][6] 이는 '열린 서브루틴' 또는 매크로와는 대조적이다.[7]천공 카드나 테이프를 통해 로드된 프로그램 명령어들을 메모리에 올리는 초기 컴퓨터에서, 각 서브루틴은 별도의 테이프 조각으로 제공될 수 있었고, 메인 프로그램 전후에 로드되거나 연결될 수 있었다. 그리고 동일한 서브루틴 테이프를 여러 다른 프로그램에서 사용할 수 있었다. 천공 카드를 사용하는 컴퓨터에서도 유사한 접근 방식이 사용되었다. ''서브루틴 라이브러리''라는 이름은 원래 공동 사용을 위해 테이프 또는 카드 덱의 색인화된 모음을 보관하는 라이브러리를 의미했다.
현대 프로그래밍 언어는 함수를 정의하고 호출하는 기능을 제공하며, 이러한 기능에 접근하기 위한 구문을 포함한다. 호출 가능 개체는 프로그램 내에서 정의되거나 여러 프로그램에서 사용할 수 있는 라이브러리에서 별도로 정의될 수 있다.
프로그램을 함수로 나누면 다음과 같은 장점이 있다.
5. 15. 상호 운용성 (Inter-operability)
컴파일러는 함수 호출 및 반환 문을 잘 정의된 호출 규약에 따라 기계어로 변환한다.[31] 동일하거나 호환되는 컴파일러로 컴파일된 코드는, 함수를 호출하는 프로그램과 별도로 컴파일될 수 있다. 환경에 따라서는 호출 규약을 규정하고 인수와 반환값의 데이터 타입을 기본 수치형이나 포인터형 등의 Plain Old Data|POD영어형으로 한정함으로써, 서로 다른 언어 간에 상호 운용 가능한 ABI 호환성이 있는 서브루틴을 정의할 수 있다.[38][39] 예를 들어 C로 작성된 함수를 DLL이나 공유 라이브러리 (so)에 익스포트하고, 파스칼의 함수 또는 프로시저로 호출하는 것도 가능하다.5. 16. 내장 함수 (Built-in functions)
''내장 함수''는 컴파일러가 특별한 방식으로 처리하는 함수이다.[23] 내장 함수는 프로그래밍 언어에 ''내장''되어 있으므로 별도로 정의할 필요가 없다.[24]함수를 호출하는 것은 인라인 코드를 사용하는 것에 비해 호출 메커니즘에서 어느 정도의 계산 오버헤드를 발생시킨다. 함수는 일반적으로 함수의 진입점과 종료점 모두에서 표준 하우스키핑 코드(함수 프롤로그 및 에필로그 - 일반적으로 최소한 범용 레지스터와 반환 주소를 저장)를 필요로 한다.
6. 프로그래밍
함수는 복잡한 프로그래밍 작업을 더 작은 단계로 분해하는 데 사용된다. 대부분의 현대적인 프로그래밍 언어는 함수를 정의하고 호출하는 기능을 제공하며, 이를 위한 구문을 포함한다. 여기에는 함수 구현의 구분, 식별자 할당, 형식 매개변수 및 반환 값의 자료형 정의, 함수 호출, 실제 매개변수 제공, 반환 제어, 반환 값 사용 또는 폐기, 변수의 명명 범위 제공, 예외 처리, 함수를 모듈, 라이브러리, 객체 또는 클래스와 같은 컨테이너로 패키징하는 것이 포함된다.[21]
반복적으로 사용되는 루틴 작업은 모듈로 묶어 "서브루틴"이라고 부르며, 이는 호출하는 쪽의 "주"가 되는 메인 루틴과 대비된다. 서브루틴화는 가독성과 유지 보수성을 높이고, 캐시 메모리를 가진 컴퓨터에서 고속 동작을 기대할 수 있게 한다.
인수를 받아 값을 반환하는 점은 수학의 함수와 유사하지만, 프로그래밍 언어에서의 함수는 상태를 가지거나, 부작용을 일으키거나, 반환값이 없을 수 있다는 차이점이 있다. 함수형 프로그래밍 언어에서는 상태나 부작용이 없는 것을 기본으로 하여 수학의 함수에 가까운 성질을 가진다.
객체 지향 프로그래밍 언어에서는 객체나 클래스에 속하는 서브루틴을 '''메서드'''라고 부른다. C++에서는 '''멤버 함수''', 자바나 C#에서는 메서드 형태로만 서브루틴을 기술해야 한다. C++나 Swift는 함수를 네임스페이스 스코프에 직접 정의할 수 있다.
서브루틴 개념은 1951년 ''The Preparation of Programs for an Electronic Digital Computer''에서도 다뤄질 정도로 오래되었다.
6. 1. 장단점 (Trade-offs)
함수는 코드 중복을 줄이고 재사용성을 높이지만, 함수 호출에는 실행 시간 오버헤드가 발생할 수 있다.[21]호출 시 발생하는 오버헤드에는 다음이 포함될 수 있다.
- 호출 스택 저장 공간 할당 및 회수
- 프로세서 레지스터 저장 및 복원
- 입력 변수 복사
- 호출 후 호출자의 컨텍스트에 값 복사
- 반환 코드의 자동 테스트
- 예외 처리
- 객체 지향 언어의 가상 메서드와 같은 디스패칭
이러한 호출의 런타임 비용을 최소화하기 위해 다양한 기술이 사용된다.
6. 1. 1. 장점 (Advantages)
함수를 사용하면 다음과 같은 장점들이 있다.- 복잡한 작업 분해: 복잡한 프로그래밍 작업을 더 간단한 단계로 나눌 수 있다. 이것은 구조적 프로그래밍의 핵심 도구 중 하나이다.[21]
- 코드 중복 감소: 프로그램 내에서 같은 코드를 반복해서 작성하지 않아도 되므로, 코드의 양을 줄일 수 있다.[21]
- 코드 재사용: 작성한 함수를 다른 부분이나 다른 프로그램에서도 다시 사용할 수 있다.[21]
- 구현 세부 정보 숨김: 함수를 사용하는 사람은 함수의 내부 작동 방식을 알 필요 없이, 함수의 기능만 알면 된다. 이를 정보 은닉이라고 한다.[21]
- 가독성 향상: 코드 블록을 설명적인 함수 이름으로 대체하여 코드를 더 읽기 쉽게 만들 수 있다.[21]
- 추적성 향상: 대부분의 프로그래밍 언어는 함수 호출 추적 기능을 제공하여, 코드의 어느 부분에서 문제가 발생했는지 쉽게 찾을 수 있도록 도와준다.[21]
6. 1. 2. 단점 (Disadvantages)
함수를 호출하면 인라인 코드를 사용하는 것에 비해 호출 메커니즘에서 어느 정도의 계산 오버헤드가 발생한다.[21] 함수는 일반적으로 함수 프롤로그 및 에필로그 등 표준 하우스키핑 코드를 필요로 하며, 이는 최소한 범용 레지스터와 반환 주소를 저장하는 것을 포함한다.6. 2. 프로그래밍 관례 (Conventions)
많은 프로그래밍 관례가 호출 가능 객체와 관련하여 개발되었다.[21]함수 이름을 지정할 때, 많은 개발자는 특정 작업을 수행할 때는 동사로 시작하는 구문으로 이름을 짓고, 문의를 할 때는 형용사로, 변수를 대체하는 데 사용될 때는 명사로 이름을 짓는다.
일부 프로그래머는 함수가 정확히 하나의 작업만 수행해야 하며, 둘 이상의 작업을 수행하는 경우 여러 개의 함수로 분할해야 한다고 주장한다. 그들은 함수가 소프트웨어 유지보수의 핵심 구성 요소이며, 프로그램 내에서 역할이 뚜렷하게 유지되어야 한다고 주장한다.[21]
모듈 프로그래밍 지지자들은 각 함수가 나머지 코드베이스에 최소한의 의존성을 가져야 한다고 옹호한다. 예를 들어, 전역 변수의 사용은 일반적으로 현명하지 않다고 여겨지는데, 이는 전역 변수를 사용하는 모든 함수 간에 결합을 추가하기 때문이다. 이러한 결합이 필요하지 않다면, 전달된 매개변수를 대신 받도록 함수를 리팩토링할 것을 권장한다.[21]
7. 언어별 예제
비주얼 베이직은 함수를 성격에 따라 `Sub`와 `Function`으로 구분한다.[54] `Sub`은 독립적으로 프로그램에 접근하며 값을 반환하지 않지만, `Function`은 특정 값을 반환할 수 있다. `Sub`은 호출한다고 하며 `Call()`을 사용하고, `Function`은 값을 대입하도록 한다. `Function`형 함수에 특별히 자료형을 부여할 필요는 없다.
다음은 비주얼 베이직의 함수 예시이다.
```vb
Private Sub Note ()
Print ("안녕하세요")
End Sub
Private Sub frmMain_Load ()
Call Note ()
End Sub
```
위 코드는 `frmMain`이 로드될 때 호출된 `Note()`가 '안녕하세요'를 출력한다.
```vb
Private Function Note ()
Note = "안녕하세요"
End Function
Private Sub frmMain_Load ()
Dim Text as String
Text = Note ()
Print (Text)
End Sub
```
위 코드는 `frmMain`이 로드될 때 호출한 `Note`가 반환한 '안녕하세요'를 출력한다.
C와 C++에서는 함수와 서브루틴을 엄격하게 구별하지 않는다. 대신 반환형은 항상 일치해야 하며, 값을 반환하지 않는 함수는 `void`로 선언한다.[41][42] 메인 함수보다 나중에 추가된 함수는 함수의 원형(프로토타입)을 선언해야 한다.
다음은 인자를 받지 않고 반환값도 없는 C 함수의 예시이다.
```c
void my_func(void)
{
// do something
}
```
함수의 원형은 보통 함수의 반환형, 이름, 인자 목록을 그대로 쓰고 중괄호 대신 세미콜론으로 끝내는 형식으로 선언한다.
```c
// header
int my_func(char* your_name);
// code
int my_func(char* your_name)
{
// ...
return x;
}
```
C++에서는 함수의 반환형과 이름이 같아도 인자 목록의 개수나 자료형이 다르면, 호출 시 사용한 인자에 따라 적절한 함수가 호출되도록 컴파일되는 오버로딩 함수를 지원한다.
7. 1. BASIC
초기 BASIC 변종은 각 줄에 고유한 숫자(줄 번호)가 필요했으며, 호출 가능한 코드 분리, 인수 전달, 값 반환 메커니즘이 없었고, 모든 변수는 전역 변수였다. 이 버전은 "sub procedure", "subprocedure" 또는 "subroutine"의 줄임말인 `GOSUB` 명령을 제공했다. 제어는 지정된 줄 번호로 이동한 다음 반환 시 다음 줄에서 계속되었다.[4]```basic
10 REM BASIC 프로그램
20 GOSUB 100
30 GOTO 20
100 INPUT “숫자를 입력하세요”; N
110 PRINT “의 제곱근은”; N;
120 PRINT “입니다”; SQRT(N)
130 RETURN
```
위 코드는 사용자에게 숫자를 반복적으로 입력하도록 요청하고 해당 값의 제곱근을 보고한다. 100-130행은 호출 가능하다.
마이크로소프트 스몰 베이직은 텍스트 기반 언어로 프로그래밍을 처음 배우는 학생들을 대상으로 하며, 호출 가능한 단위를 ''서브루틴''이라고 부른다.
`Sub` 키워드는 서브루틴의 시작을 나타내며 그 뒤에 이름 식별자가 온다. 그 다음 줄은 본문이며, `EndSub` 키워드로 끝난다.
Sub SayHello
TextWindow.WriteLine("Hello!")
EndSub
이것은 `SayHello()`로 호출할 수 있다.
고전적인 BASIC에는 GOSUB 명령을 사용한 서브루틴이 있었다. 인터프리터는 GOSUB 명령을 발견하면, GOSUB 명령의 끝 위치(주소)를 인터프리터 내의 스택에 푸시하여 저장하고, 명령으로 지정된 행으로 점프하여 실행을 계속한다. 그 후, 실행 중에 RETURN 명령을 발견하면, 스택에서 아까 저장해 둔 호출원의 위치를 팝하여 꺼내어, 그곳으로 점프하여 GOSUB 명령의 다음 명령부터 실행을 재개한다. 서브루틴을 만들고 싶을 때는, 사용자는 "이 행부터 이 행까지를 서브루틴으로 한다"라고 정하고 프로그램을 만들었다. "RETURN 할 수 있는 GOTO"이므로 (인수도 반환값도 지역 변수도 없다), (전역) 변수를 경유하거나, 배열을 사용자 스택으로 사용하는 등, 기교가 필요했다. 비교적 고기능의 구현에서는, DEFFN 명령을 통해, 식 하나로 기술할 수 있는 범위라는 제한이 있지만, 인수와 반환값이 있는 사용자 정의 함수의 추가(확장)가 가능한 것도 있었다.
7. 2. C/C++
C와 C++에서 함수는 반환 형식을 지정해야 하며, 값을 반환하지 않는 함수는 `void` 형식을 사용한다.[41][42] 함수를 메인 함수보다 나중에 배치하는 경우에는 함수의 원형(프로토타입)을 선언해야 한다.다음은 인자를 받지 않고 반환값도 없는 함수의 예시이다.
void my_func(void)
{
// do something
}
함수의 원형은 보통 함수의 반환형, 이름, 인자 목록을 그대로 쓰고 중괄호 대신 세미콜론으로 끝내는 형식으로 선언한다.
// header
int my_func(char* your_name);
// code
int my_func(char* your_name)
{
// ...
return x;
}
C++에서는 클래스 내에 선언된 함수(비정적)를 ''멤버 함수'' 또는 ''메서드''라고 부른다.[29] 클래스 외부의 함수는 멤버 함수와 구별하기 위해 ''자유 함수''라고 부를 수 있다.
C++에서의 함수 예시는 다음과 같다.
void doSomething() {
/* some code */
}
이 함수는 값을 반환하지 않으며 `doSomething()`과 같이 항상 독립적으로 호출된다.
int giveMeFive() {
return 5;
}
이 함수는 정수 값 5를 반환하며, `y = x + giveMeFive()`와 같이 독립 실행형이거나 표현식 내에 있을 수 있다.
void addTwo(int *pi) {
- pi += 2;
}
이 함수는 부작용을 가지고 있는데, 입력 값에 주소로 전달된 값을 2만큼 수정한다. 변수 `v`에 대해 `addTwo(&v)`와 같이 호출할 수 있으며, 여기서 앰퍼샌드(&)는 컴파일러에게 변수의 주소를 전달하도록 지시한다. 호출 전에 v가 5이면 호출 후에는 7이 된다.
void addTwo(int& i) {
i += 2;
}
이 함수는 C++에서만 사용 가능하며 C로 컴파일되지 않는다. 앞의 예와 동일한 동작을 하지만 실제 매개변수를 주소 대신 참조로 전달한다. `addTwo(v)`와 같은 호출은 호출 구문 없이 컴파일러가 참조로 전달을 처리하므로 앰퍼샌드를 포함하지 않는다.
7. 3. PL/I
PL/I에서 호출된 프로시저는 문자열 길이와 배열 경계와 같은 인수에 대한 정보를 제공하는 ''설명자''를 전달받을 수 있다.[21] 이렇게 하면 프로시저가 더욱 일반화될 수 있으며 프로그래머가 그러한 정보를 전달할 필요가 없어진다. 기본적으로 PL/I는 참조로 인수를 전달한다. 2차원 배열의 각 요소의 부호를 변경하는 함수는 다음과 같다.```
change_sign: procedure(array);
declare array(*,*) float;
array = -array;
end change_sign;
```
이 함수는 다음과 같이 다양한 배열로 호출될 수 있다.
```
/* 첫 번째 배열의 경계는 -5에서 +10, 3에서 9 */
declare array1 (-5:10, 3:9)float;
/* 두 번째 배열의 경계는 1에서 16, 1에서 16 */
declare array2 (16,16) float;
call change_sign(array1);
call change_sign(array2);
7. 4. Python
파이썬에서 `def` 키워드는 함수 정의를 시작할 때 사용한다. 함수 본문의 내용은 다음 줄에 들여쓰기하여 작성하며, 첫 번째 줄과 같은 들여쓰기 수준이거나 파일의 끝에서 함수가 종료된다.[30]```python
def format_greeting(name):
return "Welcome " + name
def greet_martin():
print(format_greeting("Martin"))
```
`format_greeting` 함수는 호출자가 전달한 이름을 포함하는 인사말 텍스트를 반환한다. `greet_martin` 함수는 `format_greeting` 함수를 호출하며, `greet_martin()`과 같이 호출하면 콘솔에 "Welcome Martin"을 출력한다.
7. 5. Prolog
Prolog에서 논리적 함축은 목표 감소 절차처럼 작동한다.[21]7. 6. Pascal
파스칼에서는 `function` 및 `procedure`라는 예약어를 사용하여 함수를 선언한다.[40]7. 7. Perl
펄에서는 사용자가 정의한 것을 서브루틴이라고 부르며, 인수를 전달하고 값을 반환할 수 있다. 서브루틴 내부에서는 전달된 인수에 특수 변수를 통해 접근하고, 값을 반환하기 위해 return 문[43]을 사용한다.7. 8. Visual Basic
비주얼 베이직에서는 함수를 성격에 따라 Sub와 Function으로 구분하는데, Sub은 독립적으로 프로그램에 접근하며 값을 반환하지 않으나 Function은 특정 값을 반환할 수 있다.[54] Sub은 호출한다고 하며 Call()을 사용하고, Function은 값을 대입하도록 한다. Function형 함수에 특별히 자료형을 부여할 필요는 없다.VB, VBA, VB.NET에서는 서브루틴을 프로시저라고 통칭하며, 값을 반환하는 프로시저를 "Function 프로시저", 값을 반환하지 않는 프로시저를 "Sub 프로시저"라고 부른다.[27] 프로퍼티를 정의하기 위한 구문으로 "Property 프로시저"가 있다.
후기 버전의 VB, VB.NET 및 VB6을 포함하여, 호출 가능한 단위 개념에는 ''프로시저''라는 용어가 사용된다. 값을 반환하지 않는 데는
Sub
키워드를 사용하고, 값을 반환하는 데는 Function
키워드를 사용한다. 클래스의 컨텍스트에서 사용될 때 프로시저는 메서드이다.각 매개변수는 지정할 수 있는 데이터 형식을 가지지만, 지정하지 않으면 .NET 기반의 후기 버전에서는 `Object`로, VB6에서는 variant로 기본 설정된다.[28]
VB는
ByVal
및 ByRef
키워드를 통해 값 호출 및 참조 호출 매개변수 전달 규칙을 지원한다. ByRef
가 지정되지 않으면 인수는 ByVal
로 전달된다. 따라서 ByVal
은 명시적으로 지정되는 경우가 드물다.숫자와 같은 단순한 유형의 경우 이러한 규칙은 비교적 명확하다.
ByRef
를 전달하면 프로시저가 전달된 변수를 수정할 수 있지만, ByVal
을 전달하면 수정할 수 없다. 객체의 경우 객체는 항상 참조로 처리되므로 의미 체계가 프로그래머를 혼동시킬 수 있다. 객체를 ByVal
로 전달하면 객체의 상태가 아닌 참조가 복사된다. 호출된 프로시저는 해당 메서드를 통해 객체의 상태를 수정할 수 있지만, 실제 매개변수의 객체 참조는 수정할 수 없다.예시는 다음과 같다.
Private Sub Note ()
Print ("안녕하세요")
End Sub
Private Sub frmMain_Load ()
Call Note ()
End Sub
frmMain이 로드되면 호출된 Note()가 '안녕하세요'를 출력한다.
Private Function Note ()
Note = "안녕하세요"
End Function
Private Sub frmMain_Load ()
Dim Text as String
Text = Note ()
Print (Text)
End Sub
이 프로그램에서는 frmMain이 로드되면 호출한 Note가 반환한 '안녕하세요'를 출력하게 된다.
Sub DoSomething()
' Some Code Here
End Sub
이것은 값을 반환하지 않으며
DoSomething
과 같이 독립적으로 호출해야 한다.Function GiveMeFive() as Integer
GiveMeFive= 5
End Function
이것은 값 5를 반환하며,
y = x + GiveMeFive()
와 같은 표현식의 일부가 될 수 있다.Sub AddTwo(ByRef intValue as Integer)
intValue = intValue + 2
End Sub
이것은 부작용이 있다. 참조로 전달된 변수를 수정하며
v
와 같은 변수에 대해 AddTwo(v)
와 같이 호출될 수 있다. 호출 전에 v가 5이면 호출 후에는 7이 된다.7. 9. 기타
Microsoft Excel의 함수는 주로 계산, 데이터 검색 및 집계, 표시 변환 등을 수행하는 데 사용된다.[46] 조건 연산자에 해당하는 IF 함수, 합계를 구하는 SUM 함수, 평균을 구하는 AVERAGE 함수 등 100개가 넘는 내장 "워크시트 함수"가 존재한다. 한편, VBA 코드에서만 사용할 수 있는 "VBA 함수"도 있다. 예를 들어 IIf 함수는 IF 함수와 유사한 기능을 하지만, 워크시트의 수식 내에서는 사용할 수 없고 VBA에서만 사용할 수 있다. 워크시트 함수와 VBA 함수 모두 사용자 정의 함수를 등록하여 사용할 수 있다.8. 재귀 호출 (Recursive call)
함수가 지원되는 경우, 호출 가능한 개체는 자신을 호출할 수 있으며, 동일한 호출 가능한 개체의 다른 "중첩된" 실행이 실행되는 동안 실행을 일시 중단시킬 수 있다.[54] 재귀는 일부 복잡한 알고리즘을 단순화하고 복잡한 문제를 해결하는 데 유용한 수단이다. 재귀적 언어는 각 호출 시 새로운 지역 변수 복사본을 제공한다. 프로그래머가 지역 변수 대신 동일한 변수를 사용하도록 재귀 호출 가능 개체를 원하는 경우 일반적으로 정적 또는 전역과 같은 공유 컨텍스트에서 이를 선언한다.
재귀를 통해 수학적 귀납법 및 재귀적인 분할 정복 알고리즘에 의해 정의된 기능을 직접 구현할 수 있다. 다음은 피보나치 수열을 찾기 위한 C/C++의 재귀 함수의 예시이다.
int Fib(int n) {
if (n <= 1) {
return n;
}
return Fib(n - 1) + Fib(n - 2);
}
어떤 서브루틴 `f`의 정의 내에서 `f` 자신을 다시 호출하는 것을 '''재귀 호출'''(recursive function call)이라고 한다.[47][31] 재귀 호출을 사용하면, 어떤 처리를 통해 얻은 값에 대해 동일한 처리를 여러 번 반복해야 하는 경우 간결하게 기술할 수 있다.[48] 예를 들어 팩토리얼 계산[31], 파일 시스템과 같은 트리 구조 탐색[49], 오셀로, 체스, 장기, 바둑 등의 소프트웨어에서 "미리 보기" 기능을 구현하는 데 사용될 수 있다.[50][51][52][53]
일반적인 컴퓨터 및 프로그래밍 언어에서는 서브루틴 호출 시 콜 스택에 인자나 지역 변수 등의 영역이 확보되지만, 개인용 컴퓨터와 같이 비교적 자원이 넉넉한 환경에서도 이 콜 스택의 용량은 기본적으로 스레드마다 수 MiB 정도로 설정되어 있으며, 서브루틴의 호출 계층이 너무 깊어지면 스택 오버플로우를 일으킬 수 있다. 특히 재귀 호출은 호출 계층이 깊어지기 쉽고, 재귀 횟수가 너무 많으면 스택 오버플로우를 일으키기 쉽다.
참조
[1]
웹사이트
Terminology Glossary
https://pages.nist.g[...]
NIST
2024-02-09
[2]
서적
The Art of Computer Programming, Volume I: Fundamental Algorithms
Addison-Wesley
[3]
서적
Structured Programming
Academic Press
[4]
서적
The Origins of Digital Computers
Springer
1982
[5]
간행물
Proceedings of the 1952 ACM national meeting (Pittsburgh) on - ACM '52
[6]
서적
Preparation of Programs for an Electronic Digital Computer
Addison-Wesley
[7]
백과사전
"open subroutine." A Dictionary of Computing
http://www.encyclope[...]
2013-01-14
[8]
서적
Report by Dr. A.M. Turing on proposals for the development of an Automatic Computing Engine (ACE): Submitted to the Executive Committee of the NPL in February 1946
[9]
간행물
Proposals for Development in the Mathematics Division of an Automatic Computing Engine (ACE)
1946-03-19
[10]
학술지
The other Turing machine
1977-01-01
[11]
웹사이트
Walter Isaacson on the Women of ENIAC
http://fortune.com/2[...]
2018-12-14
[12]
보고서
Report on the Mathematical and Logical aspects of an Electronic Computing Instrument
1947
[13]
서적
The Operational Characteristics of the Processors for the Burroughs B5000
http://bitsavers.org[...]
Burroughs Corporation
2024-02-08
[14]
서적
Programmed Data Processor 6 - Handbook
http://bitsavers.org[...]
2024-02-08
[15]
문서
Guy Lewis Steele Jr.
[16]
서적
Introduction to the PDP-11 and Its Assembly Language
https://books.google[...]
Prentice-Hall
2016-07-06
[17]
서적
PThreads Programming: A POSIX Standard for Better Multiprocessing
https://books.google[...]
"O'Reilly Media, Inc."
1996
[18]
웹사이트
ARM Information Center
http://infocenter.ar[...]
Infocenter.arm.com
2013-09-29
[19]
웹사이트
x64 stack usage
https://docs.microso[...]
Microsoft
2019-08-05
[20]
웹사이트
Function Types
http://msdn.microsof[...]
Msdn.microsoft.com
2013-09-29
[21]
서적
Clean Code: A Handbook of Agile Software Craftsmanship
https://www.oreilly.[...]
Pearson PLC
2024-05-19
[22]
서적
Adventures Between Lower Bounds and Higher Altitudes: Essays Dedicated to Juraj Hromkovič on the Occasion of His 60th Birthday
Springer
2018
[23]
웹사이트
Built-in functions
https://www.ibm.com/[...]
2023-12-25
[24]
서적
Study Material Python
https://books.google[...]
2023-12-25
[25]
웹사이트
Small Basic
https://smallbasic-p[...]
2024-02-08
[26]
웹사이트
Small Basic Getting Started Guide: Chapter 9: Subroutines
https://social.techn[...]
Microsoft
2024-01-17
[27]
웹사이트
Procedures in Visual Basic
https://learn.micros[...]
2024-02-08
[28]
웹사이트
Dim statement (Visual Basic)
https://learn.micros[...]
2024-02-08
[29]
웹사이트
what is meant by a free function
https://stackoverflo[...]
[30]
웹사이트
4. More Control Flow Tools — Python 3.9.7 documentation
https://docs.python.[...]
[31]
문서
大滝みや子『2020年版 基本情報技術者 標準教科書』オーム社、2019年。pp.95-96「手続きと関数」「再帰呼び出し」の章
[32]
문서
電子情報通信学会『知識の森』 - 6 群「基礎理論とハードウェア」 - 3 編「アルゴリズムとデータ構造」 - 1 章「アルゴリズムとアルゴリズム解析」
https://www.ieice-hb[...]
[33]
문서
Calling Conventions | Microsoft Learn
https://learn.micros[...]
[34]
문서
Argument Passing and Naming Conventions | Microsoft Learn
https://learn.micros[...]
[36]
웹사이트
x64 calling convention | Microsoft Learn
https://learn.micros[...]
[37]
웹사이트
Overview of ARM ABI Conventions | Microsoft Learn
https://learn.micros[...]
[38]
웹사이트
Working with DLLs | Microsoft Learn
https://learn.micros[...]
[39]
웹사이트
Developing DLLs | Microsoft Learn
https://learn.micros[...]
[40]
문서
C言語と違い、中身の記述すなわち定義まで含むものもPascalでは「宣言」と言う。
[41]
서적
The C Programming Language, Second Edition
1988
[42]
문서
標準化以前(K&R初版時代のC)は、省略した場合のデフォルトとしてintを返すと解釈される仕様だったという経緯があり、互換性を保つためにそれが標準とされたため、値を返さない場合にはvoid
を書いて明示しなければならない、という仕様になっている。
[43]
웹사이트
return statement
http://perldoc.perl.[...]
[44]
웹사이트
Writing a property procedure (VBA) | Microsoft Learn
https://learn.micros[...]
[45]
웹사이트
Property プロシージャ - Visual Basic | Microsoft Learn
https://learn.micros[...]
[46]
웹사이트
IIf function (Visual Basic for Applications) | Microsoft Docs
https://docs.microso[...]
[47]
뉴스
自分から自分を呼ぶ? Pythonで「再帰呼び出し」の不思議を体験 | 日経クロステック(xTECH)
https://xtech.nikkei[...]
日経クロステック(xTECH)
[48]
서적
令和04年 栢木先生の基本情報技術者教室
技術評論社
2021
[49]
웹사이트
.NET TIPS [ASP.NET]データベースからツリー・メニューを生成するには? - C# VB.NET Webフォーム - @IT
https://atmarkit.itm[...]
[50]
웹사이트
Chess programming
https://www.chesspro[...]
[51]
간행물
将棋ソフト作成入門
https://event.ospn.j[...]
[52]
웹사이트
再帰呼び出し
https://kaityo256.gi[...]
[53]
웹사이트
囲碁プログラムの作り方(基本編)
http://usapyon.game.[...]
[54]
AI Memo
'Debunking the "Expensive Procedure Call" Myth; or, Procedure call implementations considered harmful'
http://dspace.mit.ed[...]
본 사이트는 AI가 위키백과와 뉴스 기사,정부 간행물,학술 논문등을 바탕으로 정보를 가공하여 제공하는 백과사전형 서비스입니다.
모든 문서는 AI에 의해 자동 생성되며, CC BY-SA 4.0 라이선스에 따라 이용할 수 있습니다.
하지만, 위키백과나 뉴스 기사 자체에 오류, 부정확한 정보, 또는 가짜 뉴스가 포함될 수 있으며, AI는 이러한 내용을 완벽하게 걸러내지 못할 수 있습니다.
따라서 제공되는 정보에 일부 오류나 편향이 있을 수 있으므로, 중요한 정보는 반드시 다른 출처를 통해 교차 검증하시기 바랍니다.
문의하기 : help@durumis.com