함수 정의

함수 정의 (Function definitions)

함수 정의는 함수 본문(선언과 문장의 나열)을 함수 이름과 매개변수 목록에 연결해요. 함수 선언과 달리, 함수 정의는 파일 스코프에서만 허용돼요 (중첩 함수는 없어요).

C는 두 가지 형태의 함수 정의를 지원해요.

  1. attr-spec-seq(선택) specifiers-and-qualifiers parameter-list-declarator function-body

  2. (C23까지) specifiers-and-qualifiers identifier-list-declarator declaration-list function-body

여기서

용어 설명
attr-spec-seq (C23) 함수에 적용되는 선택적 속성 목록이에요.
specifiers-and-qualifiers 다음의 결합이에요.
· 선언자에 의해 수정될 수 있고 반환 타입을 이루는 타입 지정자
· 식별자의 연결을 결정하는 저장 클래스 지정자(static, extern, 또는 없음)
· 함수 지정자 inline, _Noreturn, 또는 없음
parameter-list-declarator 함수 매개변수를 매개변수 목록으로 지정하는 함수 타입의 선언자예요.
identifier-list-declarator 함수 매개변수를 식별자 목록으로 지정하는 함수 타입의 선언자예요.
declaration-list identifier-list-declarator의 모든 식별자를 선언하는 선언들의 나열이에요. 이 선언들은 초기화자를 쓸 수 없고, 허용되는 저장 클래스 지정자register뿐이에요.
function-body 복합 문, 즉 중괄호로 감싼 선언과 문장의 나열이에요. 이 함수가 호출될 때마다 실행돼요.
  1. 신식(C89) 함수 정의예요. 이 정의는 함수 자체를 도입하면서 이후의 모든 함수 호출 표현식에 대한 함수 프로토타입으로도 작동해요. 인자 표현식에서 선언된 매개변수 타입으로의 변환을 강제해요.
int max(int a, int b)
{
    return a>b?a:b;
}

double g(void)
{
    return 0.1;
}
  1. (C23까지) 구식(K&R) 함수 정의예요. 이 정의는 프로토타입처럼 작동하지 않아서, 이후 함수 호출 표현식은 기본 인자 승격(default argument promotion)을 수행해요.
int max(a, b)
int a, b;
{
    return a>b?a:b;
}
double g()
{
    return 0.1;
}

출처: cppreference

본문

설명 (Explanation)

함수 선언과 마찬가지로, 함수의 반환 타입은 보통처럼 선언에서 타입 지정자에 의해 결정되고 선언자에 의해 수정될 수 있는데, 완전한 비-배열 객체 타입이거나 void 타입이어야 해요. 반환 타입이 cvr-한정되면, 함수 타입을 구성할 목적으로 한정되지 않은 버전으로 조정돼요.

void f(char *s) { puts(s); } // return type is void
int sum(int a, int b) { return a+b; } // return type is int
int (*foo(const void *p))[3] { // return type is pointer to array of 3 int
    return malloc(sizeof(int[3]));
}

함수 선언과 마찬가지로, 매개변수의 타입은 함수 타입을 구성할 목적으로 함수→포인터, 배열→포인터로 조정되고, 모든 매개변수 타입의 최상위 cvr-한정자는 호환 함수 타입을 판정할 때 무시돼요.

(C23까지) 함수 선언과 달리 이름 없는 형식 매개변수는 허용되지 않아요(그렇지 않으면 구식(K&R) 함수 정의에서 충돌이 생겨요). 함수 안에서 쓰이지 않아도 반드시 이름을 붙여야 해요. 유일한 예외는 특별한 매개변수 목록 (void)예요.

(C23부터) 구식(K&R) 함수 정의가 제거됐으므로 함수 정의에서 형식 매개변수는 이름이 없어도 돼요. 이름 없는 매개변수는 함수 본문 안에서 이름으로 접근할 수 없어요.

int f(int, int); // declaration
// int f(int, int) { return 7; } // Error until C23, OK since C23
int f(int a, int b) { return 7; } // definition
int g(void) { return 8; } // OK: void doesn't declare a parameter

함수 본문 안에서, 이름이 붙은 각 매개변수는 lvalue 표현식이에요. 자동 저장 기간블록 스코프를 가져요. 매개변수가 메모리에서 어떻게 배치되는지(또는 메모리에 저장되는지조차)는 명세되지 않아요. 호출 규약의 일부예요.

int main(int ac, char **av)
{
    ac = 2; // parameters are lvalues
    av = (char *[]){"abc", "def", NULL};
    f(ac, av);
}

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

func

(C99부터) 모든 function-body 안에는 블록 스코프와 정적 저장 기간을 가진 특별한 미리 정의된 변수 __func__가 있어요. 여는 중괄호 바로 뒤에 다음과 같이 정의된 것처럼 동작해요.

static const char __func__[] = "function name";

이 특별한 식별자는 때때로 미리 정의된 매크로 상수 __FILE__, __LINE__과 결합해서 쓰여요. 예를 들어 assert에서요.

참고 (Notes)

인자 목록은 선언자에 명시적으로 있어야 해요. typedef에서 상속받을 수 없어요.

typedef int p(int q, int r); // p is a function type int(int, int)
p f { return q + r; } // Error

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

게다가 구식 정의는 declaration-list의 모든 매개변수에 선언을 요구하지 않았어요. 선언이 빠진 매개변수는 타입이 int였어요.

max(a, b) // a and b have type int, return type is int
{
    return a>b?a:b;
}

결함 보고 (Defect reports)

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

더 알아보기

  • 함수 선언 문서와 짝을 이루며, 선언과 정의의 차이를 이어서 정리하면 좋아요.
  • __func__, __FILE__, __LINE__ 같은 미리 정의된 매크로가 디버깅에 어떻게 쓰이는지도 확인할 수 있어요.
  • cppreference의 함수 정의 원문에서 신식/구식 정의의 차이를 더 보면 좋아요.