C 언어 | 함수 | 함수의 선언

함수 선언은 함수의 본체(정의)가 포함되지 않은 함수 이름과 매개 변수 목록 및 반환 값만을 선언하고 지정된 함수의 존재를 컴파일러에게 알려준다. 사전에 함수를 선언하여 함수의 정의 위치에 관계없이 함수를 호출할 수 있다.

함수 선언자

기본 형식 이외의 함수를 호출하려면 함수를 그 이전에 정의해야 했다. 하나 둘 정도의 함수는 그다시 상관 없을지도 모른다. 그러나 실제 프로그램은 수십, 수백 개의 함수를 처리한다. 예를 들어 Microsoft Windows가 제공하는 함수는 1000개를 넘는다. 내부에서는 더 많은 함수가 정의되어 있는 것이다. 그것들을 모든 main() 함수보다 먼저 정의하고, 또한 함수에서 함수를 호출할 때의 관계를 파악하는 것은 불가능할 것이다.

그래서 함수 선언자라는 것이 있다. 함수 선언자는 함수의 프로토타입 선언이라고도 하고, 반환 함수 이름, 매개 변수 목록만을 제공한다. 함수 선언자는 C 언어가 발안된 당시에는 존재하지 않았다가 1989년에 표준화가 되었을 때에 추가된 사양이다.

함수 선언자는 함수의 정의보다 앞에 선언하는 것으로, 컴파일러에 함수의 이름과 매개 변수 형식 및 반환 형식을 알려준다. 이 때, 본체는 정의하지 않는다.

함수 선언자

반환형식 함수이름(매개 변수 목록);

이러한 함수 선언을 프로그램의 시작 부분으로 구성하므로써, 함수의 정의 위치를 신경 쓸 필요가 없다. 함수를 정의할 때는 물론 함수의 선언에 근거한 형태로 정의되어야 한다. 선언과 다른 형태로 정의하면, 형태의 불일치로 인해 오류가 발생한다.

코드1

#include <stdio.h>

void CharLoop(char chMark , int iNum);

int main() {
  CharLoop('*' , 30);
 printf("\n---\n");
  CharLoop('*' , 40);
 printf("\n");
  return 0;
}

void CharLoop(char chMark , int iNum) {
 int iCount;
 for(iCount = 0 ; iCount < iNum ; iCount++) {
    printf("%c" , chMark);
  }
}

코드1은 문자 chMark을 iNum번만 반복 처리하여 표시하는 함수 CharLoop() 함수를 정의하고 있다. CharLoop() 함수는 main() 함수보다 뒤에 정의되어 있지만, main() 함수보다 앞에 CharLoop() 함수를 선언한다. 따라서 main() 함수에서 CharLoop()를 호출하고 있지만, 컴파일러는 이 시점에서 CharLoop() 함수의 매개 변수와 반환 형식을 인식하고 있기 때문에 문제는 없다.

이 함수 선언자는 비교적 새로운 사양이다. 사실 사양이 제정되기 이전에는 반환 값의 데이터 형을 컴파일러에게 알리기 위해 함수 선언자가 사용되었다. 이전 함수 선언자는 매개 변수 목록에 형식 정보를 포함하지 않고, 오직 식별자(변수명)를 기술하였다. 그러나, 이런 식으로는 컴파일러가 함수에 전달되는 값의 형식을 확인할 수 없기 때문에 요즘에는 권장되지 않는다.

void Function (value1, value2)

예를 들어, 위의 코드는 이전 함수 선언자이다. 보면 알 수 있듯이, 이 함수 선언의 매개 변수 형식 정보가 포함되어 있지 않는다.

이전 함수 선언자는 과거의 언어와의 호환성을 고려하여 일반적으로 현재의 컴파일러에도 구현되어 있다. 그러나 새로 프로그램 코드를 작성하는 경우는 항상 최신 사양을 유의하여 작성하여야 하며, 오늘날 오래된 함수 선언자를 이용하는 것은 넌센스이다. 모든 함수는 새로운 함수 선언자를 이용하여 함수를 선언해야 한다.

그러나 오래된 함수 선언자의 존재를 모르는 경우, 뜻밖의 곳에서 문제가 발생한다. 예를 들어 다음과 같은 프로그램을 작성했다고 하자.

void Function ();

이 경우는 식별자를 생략한 형의 형태 정보를 포함하지 않는 이전 함수 선언자되어 버린다. 형태 정보를 명시 한다면, 그것은 새로운 함수 선언자로 인식되지만, 형태를 생략하는 기술은 이전 함수 선언자의 사양이다.

따라서 새로운 사양에 따른 함수 선언자를 신경써서 작성을 한다고 해도, 이전의 함수 선언자를 작성하는 경우가 자주 발생한다. 이는 컴파일이 되기 때문에 의외로 알아채기 힘든 실수이다.

매개 변수가 없는 함수를 선언에는 형 정보로 void를 지정한다.

void Function (void);

이것은 새로운 함수 선언자로 인식된다.

코드2

#include <stdio.h>

void Function(void);

int main() {
  Function();
 return 0;
}

void Function(void) {
 printf("Kitty on your lap\n");
}

이 프로그램은 새로운 함수 선언자를 사용하여 매개 변수가 없는 함수 선언하고 있다.

식별자(변수명)의 생략

코드1의 함수 선언자 void CharLoop(char chMark, int iNum);을 보면, 매개 변수는 형식뿐만 아니라 변수 이름도 지정하고 있다. 그러나 선언은 본체를 포함하지 않기 때문에 식별자(변수명)은 의미가 없다.

그래서 함수 선언자에는 데이터형만을 지정하고, 식별자는 생략할 수 있다. 원래 새로운 함수 선언자의 목적은 컴파일러에 자료형을 전달하여 함수 호출시 오류 검출 및 인수의 대응 강화에 있다. 식별자 작성은 이전 함수 선언자에서 이어 받은 것이며, 정의가 없는 상태에서는 큰 의미가 없다.

void Function (int, char, double);

예를 들어, 이 경우는 순서대로 int형, char형, double형의 인수를 받는 것을 나타낸다. 인수로 받는 매개 변수의 식별자는 함수의 정의로 지정하면 되는 것이다. 덧붙여서 식별자를 생략한 매개 변수와 식별자를 명확히 작성한 매개 변수를 동일한 함수 선언자에 포함할 수 있다.

void Function (int iValue char, double);

이 함수 선언자는 첫번째 매개 변수 iValue만 식별자를 포함하여 선언하고 있지만, 그 이외의 파라미터는 식별자를 생략되어 있다. 이렇게 해도 문제는 없다.

코드3

#include <stdio.h>

void CharLoop(char chMark , int);

int main() {
  CharLoop('*' , 30);
 printf("\n---\n");
  CharLoop('*' , 40);
 printf("\n");
  return 0;
}

void CharLoop(char chMark , int iNum) {
 int iCount;
 for(iCount = 0 ; iCount < iNum ; iCount++) {
    printf("%c" , chMark);
  }
}

코드3은 코드1을 개량하고 식별자를 기입한 경우와 생략한 경우의 조합으로 함수를 선언하고 있다. 함수 선언자의 식별자가 사용되는 것은 아니므로, 식별자를 지정하는 것은 다소 중복이 된다. 그래서 식별자는 생략하고 형식만 지정한다.