함수(Function)는 특정 작업을 수행하는 명령어들의 모음에 이름을 붙인 것이다.
함수는 입력을 받아서 특정한 작업을 수행하여 결과값을 반환하는 상자와 같다. 함수에는 이름이 붙어 있으며, 작업에 필요한 데이터를 전달받을 수 있고, 작업이 완료된 후에는 작업의 결과를 호출자에게 반환한다. 이러한 함수는 주로 되풀이되는 작업에 사용할 수 있다.
예를들어 10개의 * 을 출력하는 작업을 코드로 작성해보자.
for (int i = 0; i < 10; i++)
cout << "*";
만약 이러한 작업이 프로그램 안의 여러 곳에서 필요하게 된다면 위의 코드를 계속해서 작성해야 할것이며, 코드가 불필요하게 길어지게 된다.
이러한 경우 사용할 수 있는 도구가 함수이며 함수를 이용해 여러번 반복해야 되는 처리 단계를 하나로 모아서 필요할 때 언제든지 호출할 수 있다.
함수는 특징은 다음과 같이 요약할 수 있다.
함수의 종류에는 컴파일러에서 지원되는 라이브러리 함수(Library Function)와 개발자가 직접 만들어서 사용하는 사용자 정의 함수(User-Defined Function)가 있다.
함수는 다음과 같이 정의할 수 있다.
반환타입 함수이름(매개변수타입 매개변수)
{
함수내용;
return 반환값;
}
반환 타입
반환 타입은 함수가 작업을 종료한 후 호출한 곳으로 반환하는 데이터의 유형 int, float, … 을 말한다. 값이 반환된다는 것은 함수의 호출문이 반환 값으로 대체되는 것으로 이해할 수 있다.
반환값은 존재하지 않거나 1개이다.
함수 이름
함수 이름은 함수 호출시 사용한다. 함수 이름은 식별자에 대한 규칙만 따르면 어떠한 이름이라도 가능하며 보통 함수의 기능을 암시하는 이름을 부여한다.
매개 변수
매개 변수는 함수 호출 시 전달되는 인자를 저장하는 변수이다. 매개 변수는 여러 개 존재할 수도 있고, 존재하지 않을 수도 있다.
#include <iostream>
void MakeStar(int Num)
{
for (int i = 0; i < Num; ++i)
std::cout << "*";
}
int main()
{
MakeStar(10);
return 0;
}
**********
함수를 사용하려면 함수를 호출(Call) 해야 한다. 함수 호출은 함수 이름을 통해 할 수 있다. 함수의 매개 변수가 있다면 매개변수에 전달할 인수 값도 써주어야 한다.
// 함수의 호출
함수이름(인수);
함수를 호출하게 되면 현재 실행하고 있는 코드는 잠시 중단되고, 호출된 함수로 들어가 함수 몸체 안의 문장들이 순차적으로 실행된다. 호출된 함수의 실행이 끝나면 호출한 위치로 되돌아가서 중단되었던 코드의 실행을 재개한다.

인수와 매개변수는 함수 호출 시에 데이터를 주고받기 위해 필요하다. 인수(Argument)는 호출 프로그램에 의하여 실제로 전달되는 값이며 매개변수(Parameter)는 이 값을 전달받는 변수이다. 함수가 호출될 때마다 인수는 함수의 매개변수로 전달된다.

반환 값(Return Value)은 함수가 호출한 곳으로 반환하는 작업의 결과값이다. 함수는 return 문장 다음에 수식을 써주면 자신을 호출한 곳으로 값을 반환할 수 있다. return 키워드 실행 시 값을 반환하면서 함수를 빠져나간다. 인수는 여러 개가 있을 수 있으나 반환 값은 하나만 존재한다.

만약 반환값이 없다면 return 키워드 다음에 아무것도 써주지 않거나 return 문장을 사용하지 않으면 된다.
#include <iostream>
// HelloWorld 를 출력하는 함수
void PrintHelloWorld()
{
std::cout << "Hello World" << std::endl;
}
// 수(int)를 입력받아서 출력하는 함수
void PrintNumber(int number)
{
std::cout << "넘겨진 인수는 " << number << " 입니다." << std::endl;
}
// 입력 받은 수(int)에 2를 곱하고 결과를 반환하는 함수
int MultiplyBy_2(int number)
{
return number * 2;
}
// 두 수(int)를 입력받아 서로 곱한 뒤 결과를 반환하는 함수
int MultiplyBy(int number1, int number2)
{
return number1 * number2;
}
int main()
{
PrintHelloWorld();
PrintNumber(100);
PrintNumber(MultiplyBy_2(10));
PrintNumber(MultiplyBy(2, 5));
return 0;
}
Hello World
넘겨진 인수는 100 입니다.
넘겨진 인수는 20 입니다.
넘겨진 인수는 10 입니다.
함수의 호출이 끝난 뒤 돌아갈 반환 주소, 함수의 매개변수, 함수에서 선언된 지역 변수와 같은 함수 호출과 관련된 데이터는 메모리의 스택 영역에 저장된다.
함수 호출 시 스택 영역에 메모리가 할당 되며, 함수가 종료되면서 소멸된다.
스택 영역은 후입 선출(LIFO, Last-In-First-Out) 구조로 함수의 호출 정보를 차례대로 저장하며 이를 스택 프레임(Stack Frame)이라고 한다. 스택 프레임 구조 덕분에 함수의 호출이 모두 완료된 후 해당 함수가 호출되기 이전의 주소로 되돌아 갈 수 있다.
#include <iostream>
void Func1();
void Func2();
int main(void)
{
std::cout << "main 실행" << std::endl;
Func1(); // Func1() 호출
std::cout << "main 종료" << std::endl;
return 0;
}
void Func1()
{
std::cout << "Func1 실행" << std::endl;
Func2(); // Func2() 호출
std::cout << "Func1 종료" << std::endl;
}
void Func2()
{
std::cout << "Func2 실행" << std::endl;
std::cout << "Func2 종료" << std::endl;
}
main 실행
Func1 실행
Func2 실행
Func2 종료
Func1 종료
main 종료
위위 코드는 다음과 같이 작동한다.
main 함수가 호출되고 main 함수의 스택 프레임이 스택에 저장되며 “main 실행” 메시지가 출력된다.Func1 함수가 호출되면 해당 함수의 매개변수, 반환 주소 값, 지역 변수 등의 스택 프레임이 스택 영역에 저장되며 “Func1 실행” 메시지가 출력된다.Func2 함수가 호출되면 해당 함수의 매개변수, 반환 주소 값, 지역 변수 등의 스택 프레임이 스택영역에 저장되며 “Func2 실행” 메시지가 출력된다.Func2 함수가 “Func2 종료” 메시지를 출력한 뒤 모든 작업이 완료되면 반환 주소 값을 통해 반환 되고, Func2 함수의 스택 프레임은 제거된다.Func1 함수로 돌아와 “Func1 종료” 메시지를 출력한 뒤 모든 작업이 완료되면 반환 주소 값을 통해 반환되고, Func1 함수의 스택 프레임은 제거된다.main 함수로 돌아와 “main 종료” 메시지를 출력한 뒤 모든 작업이 완료되면 main 함수의 스택 프레임이 제거되면서 프로그램이 종료된다.아래의 그림은 위의 예제 코드의 함수 호출에 의한 스택 프레임 변화를 보여주는 그림이다.


스택의 모든 공간을 다 차지하고 난 후 더 이상의 여유 공간이 없을 때 스택 프레임을 저장하게 된다면, 해당 데이터는 스택 영역을 넘어가서 저장되게 된다.
이러한 경우를 스택이 오버플로(Overflow) 된다고 하며 이를 방치할 경우 해당 프로그램은 오동작을 하게 되거나 보안상의 크나큰 취약점을 가지게 된다. 따라서 스택 오버플로 발생 시 일반적으로 프로그램 충돌이 발생하게 된다.

아래의 코드와 같이 재귀 함수의 호출에서 스택 오버플로가 발생할 수 있다.
#include <iostream>
void Func1()
{
std::cout << "Func1 호출" << std::endl;
Func1();
}
int main(void)
{
Func1();
return 0;
}
Func1 호출
Func1 호출
Func1 호출
Func1 호출
Func1 호출
Func1 호출
Func1 호출
Func1 호출
Func1 호출
Func1 호출

함수의 헤더 부분에 정의되어 있는 매개 변수도 일종의 지역 변수이므로 지역 변수가 지니는 특성을 가지고 있다. 지역 변수와 다른 점은 함수 호출 시에 넘겨받은 인수 값으로 초기화되어 있다는 점이다.
#include <iostream>
// 매개 변수 value -> 지역 변수의 일종
void Func(int value)
{
std::cout << "value: " << value << std::endl;
value++;
std::cout << "value: " << value << std::endl;
}
int main()
{
Func(0);
return 0;
}
value: 0
value: 1
위의 코드에서 value 는 매개 변수이고 동시에 지역 변수이다. value 는 함수가 시작되면 생성되고 함수가 종료되면 소멸되어 함수 내부에서는 위의 경우처럼 지역 변수로 사용할 수 있다.
여기서 주의할 점은 함수의 매개 변수를 변경한다고 해서 원래 인수의 값이 변경되지는 않는다는 점이다.
#include <iostream>
void Func(int value)
{
value++;
std::cout << "증가시킨 함수 매개변수 value: " << value << std::endl;
}
int main()
{
int value = 1;
std::cout << "함수 호출 전 인수 value: " << value << std::endl;
Func(value);
std::cout << "함수 호출 후 인수 value: " << value << std::endl;
return 0;
}
함수 호출 전 인수 value: 1
증가시킨 함수 매개변수 value: 2
함수 호출 후 인수 value: 1
위의 코드에서 main 함수 안의 value 값은 Func 함수의 호출로 인해 값이 변경되지 않는다. Func 함수 에서 매개 변수 value 를 변화시키지만 main 함수 안의 value 와 Func 함수 안의 value 는 완전히 다른 별도의 변수이기 때문이다. 함수 호출 시에는 인수값이 매개 변수로 복사될 뿐이다.
함수의 매개 변수는 디폴트 값 (Default Value)이라는 것을 설정할 수 있다. 디폴트 값이란 기본적으로 설정되어 있는 값을 뜻한다.
int Func_One(int num = 1)
{
return num;
}
위의 코드에서 매개 변수 선언 부분의 int num = 1 은 다음을 의미한다.