함수 선언

함수 선언 (Function declarations)

함수 선언은 함수를 가리키는 식별자를 도입하고, 선택적으로 함수 매개변수의 타입(prototype, 프로토타입)을 지정해요. 함수 선언은 (정의와 달리) 파일 스코프뿐 아니라 블록 스코프에서도 나타날 수 있어요.

출처: cppreference

본문

문법 (Syntax)

함수 선언의 선언 문법에서, 선언자에 의해 수정될 수 있는 타입 지정자 나열은 반환 타입을 나타내요(배열이나 함수 타입이 아닌 어떤 타입이든 될 수 있어요). 그리고 선언자는 세 가지 형태 중 하나예요.

  1. noptr-declarator ( parameter-list ) attr-spec-seq(선택)

  2. (C23까지) noptr-declarator ( identifier-list ) attr-spec-seq(선택)

  3. noptr-declarator ( ) attr-spec-seq(선택)

여기서

용어 설명
noptr-declarator 괄호로 묶이지 않은 포인터 선언자를 제외한 어떤 선언자든 가능해요. 여기 담긴 식별자가 함수 지정자가 되는 식별자예요.
parameter-list 단일 키워드 void이거나 쉼표로 구분된 매개변수 목록이에요. 목록이 가변 인자의 줄임표 매개변수로 끝날 수 있어요.
identifier-list 쉼표로 구분된 식별자 목록이에요. 이 선언자가 구식 함수 정의의 일부로 쓰일 때만 가능해요.
attr-spec-seq (C23) 함수 타입에 적용되는 선택적 속성 목록이에요.
  1. 신식(C89) 함수 선언이에요. 이 선언은 함수 지정자 자체를 도입하면서, 이후의 모든 함수 호출 표현식에 대한 함수 프로토타입으로도 작동해요. 인자 표현식에서 선언된 매개변수 타입으로의 변환을 강제하고, 인자 개수의 컴파일 시간 검사를 수행해요.
int max(int a, int b); // declaration
int n = max(12.01, 3.14); // OK, conversion from double to int
  1. (C23까지) 구식(K&R) 함수 정의예요. 이 선언은 프로토타입을 도입하지 않아서, 이후 함수 호출 표현식은 기본 인자 승격(default argument promotion)을 수행하고, 인자 개수가 매개변수 개수와 맞지 않으면 정의되지 않은 동작을 일으켜요.
int max(a, b) 
    int a, b; // definition expects ints; the second call is undefined
{
    return a > b ? a : b;
}

int n = max(true, (char)'a'); // calls max with two int args (after promotions)

int n = max(12.01f, 3.14); // calls max with two double args (after promotions)
  1. (C23까지) 프로토타입이 없는 함수 선언. 이 선언은 프로토타입을 도입하지 않아요. (C23부터) 매개변수 목록 void와 동등한 신식 함수 선언이에요.

설명 (Explanation)

함수의 반환 타입은 보통처럼 선언에서 타입 지정자에 의해 결정되고 선언자에 의해 수정될 수 있는데, 배열이 아닌 객체 타입이거나 void 타입이어야 해요. 함수 선언이 정의가 아니라면 반환 타입은 불완전 타입일 수 있어요. 반환 타입은 cvr-한정될 수 없어요. 한정된 반환 타입은 함수 타입을 구성할 때 한정되지 않은 버전으로 조정돼요.

void f(char *s);                    // return type is void
int sum(int a, int b);              // return type of sum is int.
int (*foo(const void *p))[3];       // return type is pointer to array of 3 int

double const bar(void);             // declares function of type double(void)
double (*barp)(void) = bar;         // OK: barp is a pointer to double(void)
double const (*barpc)(void) = barp; // OK: barpc is also a pointer to double(void)

함수 선언자는 다른 선언자와 타입 지정자·한정자를 공유할 수 있다면 결합할 수 있어요.

int f(void), *fip(), (*pfi)(), *ap[3]; // declares two functions and two objects
inline int g(int), n; // Error: inline qualifier is for functions only
typedef int array_t[3];
array_t a, h(); // Error: array type cannot be a return type for a function

함수 선언이 어떤 함수의 바깥에 나타나면 그 선언이 도입하는 식별자는 파일 스코프외부 연결을 가져요. 단, static을 쓰거나 앞선 static 선언이 보이면 내부 연결이 돼요. 선언이 다른 함수 안에 나타나면 식별자는 블록 스코프를 가져요(연결은 내부나 외부 중 하나).

int main(void)
{
    int f(int); // external linkage, block scope
    f(1); // definition needs to be available somewhere in the program
}

선언의 매개변수(C23까지는 함수 정의의 일부가 아닌 선언에서)는 이름을 붙이지 않아도 돼요.

int f(int, int); // declaration
// int f(int, int) { return 7; } // Error: parameters must be named in definitions
// This definition is allowed since C23

parameter-list의 각 매개변수는 단일 변수를 도입하는 선언이에요. 여기에 추가 속성이 몇 가지 붙어요.

  • 선언자의 식별자는 (C23까지는 그 함수 선언이 함수 정의의 일부가 아니라면) 선택이에요.
int f(int, double); // OK
int g(int a, double b); // also OK
// int f(int, double) { return 1; } // Error: definition must name parameters
// This definition is allowed since C23
  • 매개변수에 허용되는 저장 클래스 지정자register뿐이고, 정의가 아닌 함수 선언에서는 무시돼요.
int f(static int x); // Error
int f(int [static 10]); // OK (array index static is not a storage class specifier)
  • 배열 타입의 매개변수는 대응하는 포인터 타입으로 조정돼요. (C99부터는 배열 선언자의 대괄호 사이에 한정자가 있으면 그 포인터도 한정될 수 있어요.)
int f(int[]); // declares int f(int*)
int g(const int[10]); // declares int g(const int*)
int h(int[const volatile]); // declares int h(int * const volatile)
int x(int[*]); // declares int x(int*)
  • 함수 타입의 매개변수는 대응하는 포인터 타입으로 조정돼요.
int f(char g(double)); // declares int f(char (*g)(double))
int h(int(void)); // declares int h(int (*)(void))
  • 매개변수 목록은 , ...로 끝날 수 있어요(C23부터는 ...만일 수도 있어요). 자세한 내용은 가변 인자 함수를 참고하세요.
int f(int, ...);
  • 매개변수는 타입 void일 수 없어요(void에 대한 포인터 타입은 가능해요). 키워드 void만으로 이뤄진 특별한 매개변수 목록은 매개변수를 받지 않는 함수를 선언할 때 쓰여요.
int f(void); // OK
int g(void x); // Error
  • 매개변수 목록에 나타나는 식별자 중 typedef 이름으로 처리될 수 있는 것은 typedef 이름으로 처리돼요. int f(size_t, uintptr_t)size_tuintptr_t 타입의 이름 없는 매개변수 두 개를 받는 신식 선언자로 파싱되지, "size_t"·"uintptr_t"라는 이름의 매개변수 두 개를 받는 함수 정의를 시작하는 구식 선언자로 파싱되지 않아요.
  • 매개변수는 불완전 타입일 수 있어요. (C99부터는 VLA 표기 [*]를 쓸 수도 있어요. 단 함수 정의에서는 배열→포인터, 함수→포인터 조정 후의 매개변수 타입이 완전해야 해요.)

(C23부터) 속성 지정자 나열을 함수 매개변수에도 적용할 수 있어요.

함수 호출의 다른 세부 동작은 함수 호출 연산자, 함수에서 돌아오는 것은 return 문서를 참고하세요.

참고 (Notes)

(C23까지) C++과 달리 선언자 f()f(void)는 의미가 달라요. f(void)는 매개변수를 받지 않는 함수를 선언하는 신식(프로토타입) 선언자예요. f()는 (함수 정의에서 쓰이지 않으면) 불특정 개수의 매개변수를 받는 함수를 선언하는 선언자예요.

int f(void); // declaration: takes no parameters
int g(); // declaration: takes unknown parameters

int main(void) {
    f(1); // compile-time error
    g(2); // undefined behavior
}

int f(void) { return 1; } // actual definition
int g(a,b,c,d) int a,b,c,d; { return 2; } // actual definition

함수 정의와 달리, 매개변수 목록은 typedef에서 상속받을 수 있어요.

typedef int p(int q, int r); // p is a function type int(int, int)
p f; // declares int f(int, int)

(C99까지) C89에서는 specifiers-and-qualifiers가 선택이었고, 생략하면 함수의 반환 타입이 int로 기본 지정됐어요(선언자가 수정할 수 있어요).

*f() { // function returning int*
   return NULL;
}

결함 보고 (Defect reports)

DR 적용 대상 발표된 동작 올바른 동작
DR 423 C89 반환 타입이 한정될 수 있었다 반환 타입이 암시적으로 한정 해제된다

더 알아보기

  • 함수 정의 문서와 짝을 이루며, 정의에서 매개변수를 어떻게 다루는지 이어서 보면 좋아요.
  • 함수 호출 연산자와 가변 인자 문서에서 호출의 세부 동작을 확인할 수 있어요.
  • cppreference의 함수 선언 원문에서 최신 표준 변경을 확인할 수 있어요.