7. C 함수 (function)
C에서 함수는 특정한 작업을 처리하는 코드의 한 단위이며, C를 구조적인 언어로 만들어 주는 가장 중요한 수단 입니다. 이번 장에서는 함수의 사용방법, 가변인자, 프로토 타입, 함수를 사용해야 하는 이유에 대해서 알아 보겠습니다.


7.1 사용 방법

이전에도 잠시 알아 보았지만, 함수의 헤더 부분은 아래와 같이 작성 됩니다.

[반환값] 함수명 ([인자], ...)

1) 반환값

반환값에는 int, float, char, ..., 사용자가 정의한 구조체(structure)등 반환(return)할 변수 타입이 옵니다.

int를 반환할 경우에는 반환값을 생략할 수 있지만, 명확히 써주는 것이 좋습니다. 함수가 코드만 수행하고 값을 반환하지 않을 때는 아래와 같이 void를 사용합니다.
void showEnemy();

반환값이 있을 경우 함수 내에서 반드시 return을 사용하여 올바른 타입으로 반환하여야 합니다. return은 반환값 내에 괄호를 할 수도 있고, 그냥 변수나 상수만 쓸 수도 있습니다. 아래 두개 모두 올바른 표현 입니다.
return 3;
return (3);


2) 함수명

함수명은 실제 그 함수의 코드를 확인하지 않고, 함수명만 보더라도 그 함수가 어떤 용도로 사용되는지 짐작할 수 있도록 작성하여야 합니다. 함수명은 변수명과 같이 아래와 같은 규칙이 적용됩니다.

  • 알파벳 대소문자([A-Z][a-z]), 숫자([0-9]), 언더바(_)를 사용할 수 있습니다.
  • 첫 글자에 숫자가 올 수 없습니다.
  • 대소문자를 구별합니다.
   
3) 인자

함수명 다음에는 괄호가 오고,  괄호 내부에는 인자들이 위치 합니다. 인자는 함수를 호출하는 곳에서 넘겨 주는 정이며, 매개변수라고도 합니다.

각 인자는 아래와 같이 , 로 구분합니다.
int get_man(int n1, int n2, int n3);

인자를 넘길 필요가 없을 때는 아래와 같이 비워 둡니다.
int get_man();

인자는 필요한 만큼의 갯수를 넘길 수 있지만, 가능한 5개를 넘지 않는 것이 좋습니다. 많은 인자가 필요할 경우에는 나중에 나올 구조체를 사용하여야 합니다.


>> 가변인자
printf와 같은 함수는 인자 수가 사용자의 필요에 따라 가변적입니다. C에서는 필요에 따라서는 가변인자를 사용할 수 있습니다. 주어진 인자들의 평균값을 출력하는 가변인자의 사용예를 보겠습니다.

이 부분은 이해가 가지 않으면, 다음 내용으로 그냥 넘어 가셔도 상관 없습니다. 포인터 및 다른 강좌가 진행된 후에 다시 확인해 보시기 바랍니다. 

#include <stdio.h>
#include <stdarg.h>

int get_average(int total, ...)
{
    int i;  
    int avr = 0;
    va_list valist;
    
    va_start(valist, total);

    for(i = 0; i < total; i++)
    {
        avr += va_arg(valist, int);
    }
    
    va_end(valist);

    avr = avr/total;

    return avr;
}

int main(int argc, char* argv[])
{
    printf("%d\n", get_average(4, 50, 100, 100, 50));

    return 0;
}

#include <stdarg.h>
va_ 류의 가변인자 함수를 사용할려면, stdarg.h 파일을 인클루드해야 합니다.

int get_average(int total, ...)
"..."는 가변인자임을 나타내며, 항상 가변인자 함수의 마지막 인자가 되어야 합니다. 또한 가변인자는 반드시 하나 이상의 일반 인자를 가지고 있어야 합니다. int get_average(...); 와 같이는 사용할 수 없습니다.

C에서 가변인자 함수는 그리 똑똑하지 않습니다. 넘어 오는 인자의 갯수와 타입을 알 수 없습니다. 그래서 printf와 같이 여러 타입을 자유롭게 사용할려면 "%d %s" 처럼 넘어 오는 인자의 갯수와 종류를 알려 주는 인자를 앞에 두어야 합니다.

여기서는 모두 int형으로 간주하므로 total 인자로 인자의 갯수를 받습니다.

va_list valist;
가변인자를 사용하기 위해 va_list 변수를 선언 합니다.

va_start(valist, total);
가변인자 처리를 시작함을 알립니다. va_start의 두번째 인자는 "..." 바로 앞의 인자(마지막 일반 인자)를 넘겨 줌으로서 스택에서 바로 다음에 오는 가변인자의 시작위치를 알립니다.
valist에는 가변인자의 첫번째 값의 포인터가 들어 갑니다.
     
avr += va_arg(valist, int);
va_arg는 가변인자의 값을 반환하여 줍니다. 두번째 인자는 변수의 타입입니다. 변수타입의 크기만큼 valist의 포인터를 이동하여, 다음 변수를 얻어 올 수 있도록 합니다.

va_end(valist);
가변인자 처리가 종료됨을 알립니다. 이 후로는 다시 가변인자를 처리할 수 없습니다.

printf("%d\n", get_average(4, 50, 100, 100, 50));
구해 온 평균값을 출력합니다. get_average의 4는 평균값을 구해올 인자가 4개 라는 의미입니다.

가변인자는 이후에 디버깅 부분에서 다시 설명하겠습니다.


7.2 프로토타입 선언
함수의 프로토 타입은 소스의 상단이나 헤더파일에서 이런 형식으로 함수가 존재 한다는 것을 미리 선언해 놓는 것을 의미합니다.

#include <stdio.h>

int main(int argc, char* argv[])
{
    printf("%d", get_number());
    return 0;
}

int get_number()
{
    return 10;
}

이전 C 컴파일러들은 위와 같은 소스를 컴파일할 때는 오류를 발생하였습니다. 컴파일이 위에서 아래로 진행이 되기 때문입니다. main에서 get_number()라는 함수가 나오는데 이에 대한 정보가 그 이전 어디에도 없기 때문입니다.

하지만 요즘의 C 컴파일러들은 뒤에 나올 함수의 프로토타입 선언 없이도 위와 같은 코드가 컴파일 되며, 다른 소스파일에 있는 함수도 컴파일이 됩니다.

하지만 프로토 타입을 선언하지 않으면, 컴파일 시 함수의 인자들에 관해 체크하지 않습니다. 이는 보이지는 않지만 실행 시 오류의 원인이 되며, 이런 오류는 찾아 내기가 힘듭니다.

이런 이유로 함수의 프로토타입 선언은 반드시 해주는 것이 좋습니다. 위의 소스에 함수의 프로토 타입을 선언해 보겠습니다.

#include <stdio.h>

int get_number();

int main(int argc, char* argv[])
{
    printf("%d", get_number());
    return 0;
}

int get_number()
{
    return 10;
}

위와 같이 함수가 나오는 상단에 함수명이 있는 부분에 세미콜론(";")을 더해 줍니다. 소스파일 상단이나 헤더파일에 함수의 프로토 타입을 작성해 두면, 컴파일 시 함수의 프로토 타입과 맞게 사용하는지 검사도 할 수 있고 어떤 소스 파일에는 어떠한 함수들이 있고 어떻게 사용하는지에 관해 한눈에 알아 볼 수 있습니다.


7.3 함수를 사용하는 이유

함수의 필요성과 효율성에 대한 예를 들어 보겠습니다. 호프집에서 준비해야할 맥주잔의 갯수를 출력하는 프로그램을 만든다고 가정합니다. 또한 이 호프집에는 아래와 같이 네 종류의 손님 그룹이 있다고 가정합니다.
  • 20대 / 남 / 평균 소비 잔 수 = 20
  • 20대 / 여 / 평균 소비 잔 수 = 10
  • 30대 / 남 / 평균 소비 잔 수 = 10
  • 30대 / 여 / 평균 소비 잔 수 =  5

아래 네 그룹을 위해 각각 준비할 잔수를 출력하는 소스 입니다.

#include <stdio.h>

#define M20_CUP         20
#define M30_CUP         10
#define W20_CUP         10
#define W30_CUP         5

int main(int argc, char* argv[])
{
    int m20, m30, w20, w30;

    m20 = 120;  
    m30 = 110;  
    w20 = 90;
    w30 = 55;

    printf("20 (M): %d\n", M20_CUP * m20);
    printf("30 (W): %d\n", M30_CUP * m30);
    printf("20 (M): %d\n", W20_CUP * w20);
    printf("30 (W): %d\n", W30_CUP * w30);
 
    return 0;
}

m20, m30, w20, w30은 그룹별 손님 수 입니다. 그룹별로 손님수와 평균 사용 맥주잔 수를 곱해서 필요한 잔수를 출력 합니다. 소스를 실행 시키면 아래와 같이 출력 됩니다.

> 20 (M): 2400
> 30 (W): 1100
> 20 (M): 900
> 30 (W): 275

여기까지는 이상이 없지만 사용자가 출력 포맷을 아래와 같이 바꾸어 줄 것을 요구합니다.
printf(">> 30 (W): %d\n", W30_CUP * w30);

이를 위해서는 위의 소스에서 printf문의 네 곳을 모두 위와 같이 변경해 주어야 합니다.

또한 계산 방법에 변경이 생겼습니다. 기존 계산 방법에서 여유로 10개씩 추가할 것을 요구하였습니다. 변경된 내용은 아래와 같습니다.
printf(">> 30 (W): %d\n", W30_CUP * w30 + 10);

위와 같이 또 관련된 부분을 모두 변경해야 합니다. 만약 그룹이 100개 라면 끔찍한 작업이 될 것 입니다. 많은 시간도 소비하고 오타가 나올 확률도 많습니다.

또 한가지 문제점은 다른 소스에서도 사용하고 있다면, 관련 파일 마다 모두 출력 포맷이나 계산방법을 변경해야 합니다. 그래서 다음과 같이 함수로 만들어 처리해 보겠습니다.

#include <stdio.h>

#define M20_CUP         20
#define M30_CUP         10
#define W20_CUP         10
#define W30_CUP         5

#define SEX_MAN         1
#define SEX_WOMAN       2

/* 나이대, 인원수, 성별을 받습니다. */
void print_cup_count(int age, int sex, int man);

int main(int argc, char* argv[])
{
    int m20, m30, w20, w30;

    m20 = 120;  
    m30 = 110;  
    w20 = 90;
    w30 = 55;

    print_cup_count(20,  SEX_MAN, m20);
    print_cup_count(30,  SEX_MAN, m30);
    print_cup_count(20,  SEX_WOMAN, w20);
    print_cup_count(30,  SEX_WOMAN, w30);
 
    return 0;
}

void print_cup_count(int age, int man, int sex)
{
    int cup;
    int count;
    char temp;

    if(sex == SEX_MAN)
    {
        if(age == 20)
            count = M20_CUP;
        else
            count = M30_CUP;

        temp = 'M';
    }       
    else
    {
        if(age == 20)
            count = W20_CUP;
        else
            count = W30_CUP;

        temp = 'W';
    }

    cup = man * count;

    printf("%d (%c): %d\n", age, temp, cup);
}

위와 같이 출력되는 부분을 조건에 맞게 함수로 만들었습니다. 이제 포맷이 변동되거나 계산방법이 변경되어도 print_cup_count 함수의 한 곳만 수정하면 모든 곳에 적용이 됩니다. 그리고 필요한 모든 곳에서 편리하게 이용할 수 있습니다.

여기서는 한줄 내용을 함수로 만들었지만, 복잡한 내용일 수록 그 효율성은 더욱 증대 됩니다.

함수나 main등에 몇 백줄의 코드가 들어 가면, 이해하거나 테스트, 오류를 찾기가 점점 힘들어 집니다. 이럴 경우도 적당한 동작별로 코드를 나누어 별개의 함수로 만드는 것이 좋습니다.

이로서 C에서 중요한 부분인 변수, 연산자, 제어문, 함수에 대한 간단한 설명을 모두 마쳤습니다. 하지만 변수, 연산자, 함수, 제어문에 관해서는 아직도 알아야 할 내용이 많이 남아 있습니다. 이는 앞으로도 계속 설명될 것입니다.

'프로그래밍 강좌 > C 언어 기초' 카테고리의 다른 글

9. 배열 (array)  (0) 2007.06.17
8. 포인터 (pointer)  (4) 2007.06.16
6. 제어문  (0) 2007.06.14
5. 연산자  (0) 2007.06.13
4. 변수  (2) 2007.06.12
AND