Skip to Main Content

C언어 기초 가이드 STEP 1: 함수

코딩 학습 가이드는 소프트웨어학과 송오영교수님이 검수하였습니다.

함수란?

함수어떤 기능을 하도록 만든 코드의 묶음입니다. 그래서 처음 한 번만 작성하면 나중에 필요할 때마다 계속 불러 사용할 수 있습니다.
예로 지금까지 사용했던 printf, scanf 등도 C 표준 라이브러리에서 기본으로 제공하는 함수입니다.

함수는 입력을 받아서 출력을 내보내는 박스로 생각할 수 있습니다.

예)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
 
int sum(int a, int b);    // sum 함수 선언
 
int main(void)
{
    int x = 3, y = 4;
    int result;
 
    result = sum(x, y);    // sum 함수 호출
    printf("result는 %d입니다.\n", result);
 
    return 0;
}
 
int sum(int a, int b)    // sum 함수 정의
{
    int hap;
 
    hap = a + b;
 
    return hap;
}

함수의 3가지 상태

함수를 만드는 것함수 정의라고 합니다. C언어에서 함수를 정의하는 방법은 다음과 같습니다. 함수를 정의하기 위해서는 이 내용을 코드 형식으로 써야 합니다.

- 반환형 : 함수가 수행된 후의 결과
- 함수이름 : 함수의 기능에 맞는 이름
- 매개변수 : 함수가 기능을 수행하는데 필요한 데이터

형식

반환형 함수이름(매개변수)
{
    함수 내용
}
 
1
2
3
4
5
6
7
8
int sum(int a, int b)
{
    int hap;
 
    hap = a + b;
 
    return hap;
}
 

 return은 함수의 실행결과를 돌려주는 제어문입니다.


 - 반환형 : sum 함수는 두 정수의 합을 반환하므로 int를 사용합니다.

 - 함수이름 : 두 정수를 더하는 함수이니 sum으로 함수명을 만들었습니다.

 - 매개변수 : sum 함수는 두 정수를 더하는 함수이므로 int형 변수를 2개 선언합니다.

함수 선언컴파일러가 새로 만든 함수를 인식할 수 있도록 함수의 형태를 알리는 역할을 합니다.
C언어에서는 가장 먼저 main() 함수가 컴파일러에 의해 컴파일됩니다. 하지만 main() 함수 뒤에 등장하는 함수는 컴파일러가 알지 못하기 때문에 오류가 발생하게 됩니다.
따라서 컴파일러에게 함수가 정의되어 있다고 main 함수 앞에 미리 알려줘야 합니다. 이 역할이 바로 '함수 선언'입니다.
필요한 함수를 main 함수 밑에 차례로 정의하고, main 함수 앞에는 모든 함수를 선언하여 작성하는 것이 좋습니다.

함수의 선언은 다음과 같은 방식으로 선언됩니다.

 

형식

반환형 함수이름(매개 변수);
   
1
int sum(int a, int b);

함수 호출만든 함수를 사용할 때 사용합니다.

함수의 이름을 적은 후 괄호를 열고 매개변수들을 넣은 후 괄호를 닫는 것으로 함수를 호출할 수 있습니다.
반환하는 데이터가 있을 경우 =(대입 연산자)를 통해 대입받을 수 있습니다.

 

형식

반환받을 변수 = 함수이름(매개변수);
   
1
result = sum(x, y);

여러 가지 함수 유형

매개변수와 반환값이 모두 있는 경우는 가장 일반적인 형태의 함수입니다.
이와 같은 함수는 매개변수도 고, 반환값도 습니다.

1
2
3
4
5
6
int Add(int num1, int num2)    // 매개변수는 int형 정수 둘이며, 이 둘을 이용한 덧셈을 진행합니다.
{
    int result = num1 + num2;
 
    return result;    // return에 의해 result의 값이 반환됩니다.
}
 

매개변수가 고, 반환값이 는 경우입니다.
반환값이 없다고 해서 값을 출력할 수 없는 것은 아닙니다.
이런 경우에는 반환할 필요가 없기 때문에 int를 쓰지 않고, void라는 반환형을 사용합니다.

1
2
3
4
5
void ShowAddResult(int num)    // 여기서 사용한 void에는 '반환하지 않는다.'라는 뜻이 담겨있습니다.
{
    printf("덧셈결과 출력 : %d\n", num);
                                           // return문이 없습니다.
}
 

매개변수가 고, 반환값이 는 경우입니다.
따라서 함수를 호출할 때 매개변수를 전달하지 않습니다.

1
2
3
4
5
6
7
int InputNum(void)    // 여기서 사용한 void에는 '매개변수를 전달하지 않는다.'라는 뜻이 담겨있습니다.
{
    int num;
    scanf("%d"&num);
 
    return num;
}
 

매개변수도 고, 반환값도 는 경우입니다.
사용방법을 소개하는 함수입니다. 이 함수는 단순히 메시지를 전달하는 함수이기 때문에 매개변수의 전달도, 반환값도 불필요합니다.
따라서 매개변수도, 반환형도 void로 선언되었습니다.

1
2
3
4
5
void HowToUse(void)
{
    printf("두개의 정수를 입력하면 덧셈결과를 출력합니다.\n");
    printf("정수를 입력 하세요.\n");
}
 

변수의 존재기간과 접근범위

지역변수는 말 그대로 한정된 지역에서만 사용되는 변수입니다.
따라서 { } (중괄호) 안에 선언되는 변수는 지역변수입니다. 그래서 함수가 중괄호를 벗어나면 사라집니다.


 
1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
 
int main(void)
{
    {
        int num = 3;
    }
 
    printf("%d\n", num);
 
    return 0;
}
 

왼쪽의 소스 코드를 컴파일해보면 컴파일 에러가 발생합니다.

식별자 "num"이(가) 정의되어 있지 않습니다.
'num':선언되지 않은 식별자입니다.

변수 num은 중괄호로 묶인 블록 안에 선언되었습니다.
블록 밖에서 num을 사용하려고 하면 변수를 찾을 수 없기 때문에
컴파일 에러가 발생합니다.


 
1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
 
int main(void)
{
    int num = 3;
    {
        printf("%d\n", num);
    }
    return 0;
}
 

이번에는 main 함수에 변수를 선언해보겠습니다.
왼쪽의 소스 코드를 컴파일해보면 컴파일 에러가 발생하지 않습니다.
변수 num은 main 함수 블록에 선언되어 있고,
printf가 있는 블록도 main 함수 블록 안에 있습니다.
따라서 num이 main 함수 블록 안에서 사용하는 것이 되므로 문제가 없습니다.


전역변수어떤 변수 영역 내에서도 접근할 수 있는 변수입니다. 지역 변수와 대비되는 개념으로써 지역변수와 달리 중괄호 내에 선언되지 않습니다.
전역변수는 프로그램 전체 영역 어디서든 접근이 가능합니다.
전역변수의 선언은 가급적 제한해야 합니다. 왜냐하면 전역변수는 프로그램의 구조를 복잡하게 만들기 때문입니다.
또한, 전역변수와 지역변수의 이름은 다르게 하는 것이 좋습니다.


 
1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
 
int num = 3;
 
int main(void)
{
    printf("%d\n", num);
 
    return 0;
}
 

num은 main 함수 바깥에 선언되어 있습니다.
이 num은 어디에서든 접근할 수 있습니다.
그리고 main 함수에서 호출한 다음에, num 값을 출력합니다.
이 때, num 값은 전역 변수 num 값입니다.

void 지역변수를 선언할 때 static 선언을 추가하면 static 변수(정적 지역 변수)가 됩니다. static 변수는 코드 블록 안에 선언하므로 일반 지역변수와 같이 사용 범위가 블록 안으로 제한됩니다.
하지만 일반 지역변수와 static 변수는 저장 공간이 메모리에 존재하는 기간이 다릅니다.

static 변수를 선언하는 방법은 지역변수를 선언하는 방법과 같으며 앞에 static만 붙여주면 됩니다.

static 자료형 변수이름;

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
 
void plusplus(void);    // plusplus 함수 선언
 
int main(void)
{
    int i;
    for (i = 0; i < 5; i++)
        plusplus();
    return 0;
}
 
 
void plusplus(void)
{
    int num1 = 0;    // 일반 지역변수로 선언
    static int num2 = 0;    // static 변수로 선언
    
    num1++;
    num2++;
 
    printf("local : %d, static : %d\n", num1, num2);
}
 
 

일반 지역변수와 static 변수를 비교해보겠습니다.

일반 지역변수는 함수가 호출될 때 메모리에 할당되고 반환될 때
메모리에서 제거됩니다.
그래서 일반 지역변수가 호출될 때마다 메모리에 새롭게 할당되고
그 때마다 1로 초기화됩니다.

static 변수는 저장 공간이 할당되어 유지하는 시점이 함수 호출과 무관합니다.
static 변수는 프로그램이 실행될 때 메모리에 할당되며
프로그램이 끝날 때까지 존재합니다.
그래서 함수가 호출될 때마다 1씩 증가하므로 1부터 5까지 출력되는 것입니다.

 

실행결과

local : 1, static : 1
local : 1, static : 2
local : 1, static : 3
local : 1, static : 4
local : 1, static : 5
 

지역변수에 register 라는 선언을 추가하면 변수는 메모리 대신 CPU의 레지스터를 사용합니다.
레지스터는 CPU 안에 있어 데이터 처리 속도가 가장 빠른 공간입니다. 따라서 일반 변수보다 속도가 빠릅니다.
register 선언 방법은 아래와 같습니다.

register 자료형 변수이름;

register 선언을 추가해도 컴파일러가 합당하지 않다고 판단하면 레지스터에 할당되지 않습니다.
       반대로 register 선언을 하지 않아도 컴파일러가 레지스터에 할당해야겠다고 판단하면 레지스터에 할당됩니다.
       최근의 컴파일러가 생성하는 코드는 최적화가 진행되고 있기 때문에, register 지정자는 거의 이용되지 않습니다.

재귀함수

재귀함수는 함수 내에서 자기 자신을 다시 호출하는 함수입니다.
재귀함수를 작성할 때는 반드시 이 함수의 호출이 끝날수 있는 지점을 마련해 주어야 합니다. 그렇지 않다면 무한루프에 빠지고 runtime error가 발생하게 됩니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
 
int factorial(int n)
{
    if (n == 0)
        return 1;
 
    return n * factorial(n - 1);
}
 
int main(void)
{
    printf("3! = %d\n", factorial(3));
 
    return 0;
}
 
 

재귀함수를 사용하여 팩토리얼(factorial)을 구현해보겠습니다.
팩토리얼은 1부터 n까지의 자연수를 차례대로 곱한 값이며 ! (느낌표) 기호로 표기합니다.

매개변수와 반환값은 자연수이기 때문에 int형으로 지정해줍니다.
factorial 함수에서는 n부터 역순으로 1씩 감소하면서 재귀호출을 합니다.
if문은 탈출조건입니다. n이 0이 되었을 때 함수가 종료되도록 정의되어 있습니다.

factorial 함수는 계산 결과가 즉시 구해지는 것이 아니라 재귀호출로 n - 1을 계속 전달하다가
n이 0일 때 비로소 1을 반환하면서 n과 곱하고 다시 결과값을 반환합니다.
그 뒤 n과 반환된 결과값을 곱하여 다시 반환하는 과정을 반복합니다.

실행결과

3! = 6
 

재귀함수는 하나의 함수에서 코드를 반복 실행하는 듯하지만 실제로는 새로운 함수를 실행하는 것과 같습니다.