Menu

C 함수: 선언, 호출, 그리고 값 반환

C에서 함수를 정의하고 호출하는 방법 - 반환 타입, 매개변수, 반환값, void 함수, 분해(decomposition)가 중요한 이유, 그리고 작은 이름 있는 조각들로 만든 완성된 예제.

이 페이지에는 실행 가능한 에디터가 있습니다 - 편집하고 실행하면 결과를 바로 볼 수 있습니다.

왜 함수인가

지금까지는 모든 것이 main 안에 있었습니다. 프로그램이 한 가지 일만 할 때는 괜찮지만, 그 이상이 되는 순간 세 가지 문제가 한꺼번에 찾아옵니다. 같은 코드 블록이 세 군데로 복사되고, main이 머릿속에 담을 수 없을 만큼 커지며, 어떤 코드 덩어리가 무엇을 위한 것인지 이름 붙일 방법이 없습니다.

함수는 이 셋을 모두 해결합니다. 코드 블록에 이름과 자기만의 지역 변수, 입력 목록, 그리고 되돌려줄 하나의 값을 부여합니다.

square는 한 번 작성되어 세 번 호출되며, 그중 한 번은 자기 자신의 결과에 다시 호출됩니다. 여러분은 이미 함수를 호출해 왔습니다. printf도 함수이고 main도 함수입니다.

함수의 구조

returnType name(parameterList) {
    // 본문
    return value;
}

네 부분으로 이루어집니다.

  • 반환 타입 - 함수가 되돌려주는 값의 타입입니다. int, double, char, 포인터 타입, 또는 "없음"을 뜻하는 void입니다.
  • 이름 - 변수 이름과 같은 규칙을 따르며, 함수가 무엇을 하는지 또는 무엇을 만들어내는지를 말해야 합니다.
  • 매개변수 목록 - 입력들이며, 각각 자기 타입을 가지고 쉼표로 구분됩니다. (void)는 입력을 받지 않는다는 뜻입니다.
  • 본문 - 중괄호 안의 문장들이며, 반환 타입이 void가 아니라면 return으로 끝납니다.

pi()도 호출하는 자리에 빈 괄호가 필요하다는 점에 주목하세요. 그 괄호가 있어야 이름을 언급하는 것이 아니라 호출이 됩니다. 그리고 printBanner(20);은 그 자체로 하나의 문장인데, 사용할 값을 만들어내지 않기 때문입니다.

매개변수가 없는 함수에는 ()가 아니라 (void)를 쓰세요. C에서 이 둘은 서로 다른 의미이고, ()가 느슨한 쪽입니다. 이에 대해서는 잠시 뒤에 더 이야기합니다.

호출과 반환

호출은 제어를 함수 안으로 넘기고, 본문을 실행한 다음, 값을 가지고 돌아옵니다. 그 값은 주변 표현식에서 호출 자리를 대신합니다.

return은 두 가지 일을 동시에 합니다. 값을 제공하고, 그 자리에서 함수를 끝냅니다. 같은 경로에서 return 뒤에 오는 것은 절대 실행되지 않으며, 그 덕분에 larger의 조기 반환(early return) 스타일이 성립합니다. else가 필요 없는 이유는 마지막 줄에 도달했다는 사실 자체가 이미 a > b가 거짓이었다는 뜻이기 때문입니다.

함수에는 return 문이 여러 개 있을 수 있습니다. 다만 한 번의 호출에서 실행되는 것은 그중 하나뿐입니다.

void 함수

값이 아니라 효과를 위해 존재하는 함수는 반환 타입이 void입니다. 출력하기, 포인터를 통해 무언가를 갱신하기, 그리기 같은 일들입니다.

void 함수 안에서 값이 없는 return;은 일찍 빠져나갑니다. 거기서 return someValue;는 컴파일 오류이고, 그 호출을 표현식에서 쓰는 것도 마찬가지입니다. int x = printTable(7, 5);는 컴파일되지 않는데, 이것이 바로 타입 시스템이 제 역할을 하는 모습입니다.

분해: 진짜 핵심

함수는 주로 반복을 피하기 위한 것이 아닙니다. 하나의 긴 절차를 이름 붙은 몇 개의 단계로 바꿔서, main이 구현이 아니라 프로그램에 대한 설명처럼 읽히게 하는 것이 핵심입니다.

이제 main은 출력 네 줄과 계산 하나입니다. 각 헬퍼 함수는 한 가지 일만 하고, 그 일이 무엇인지 말해주는 이름을 가지며, 따로 테스트할 수 있습니다. 배열의 길이가 두 번째 매개변수로 함께 전달된다는 점에 주목하세요. 배열이 함수로 전달되고 나면 그 안에서는 sizeof 트릭이 더 이상 통하지 않습니다. 배열이 포인터로 붕괴(decay)하기 때문입니다.

작업을 함수로 나눌 때의 경험 법칙 세 가지입니다.

  • 함수 하나에 일 하나. 이름에 "그리고"가 들어가야 한다면 그것은 아마 두 개의 함수입니다.
  • 필요한 것을 받고, 만들어낸 것을 반환하라. 전역 상태를 읽는 함수는 이해하기 어렵고 따로 떼어 테스트하는 것이 불가능합니다.
  • 한눈에 볼 수 있을 만큼 짧게. 마법 같은 줄 수 기준은 없지만, 화면에 다 들어가지 않는 함수는 보통 그 안에 더 작은 함수를 숨기고 있습니다.

함수가 함수를 호출하기

함수는 이미 선언된 함수라면 무엇이든, 여러분이 직접 작성한 것까지 포함해 호출할 수 있습니다.

mainreport를 호출하고, reportsumEvens를 호출하고, sumEvens가 반복문 안에서 isEven을 호출합니다. 각 호출은 호출한 쪽의 것 위에 쌓이는 자기만의 새 지역 변수 집합을 가지며, 반환할 때 버려집니다.

위의 정의들은 의존 순서대로, 즉 isEven 다음 sumEvens 다음 report 순으로 놓여 있습니다. C는 함수가 호출되기 전에 선언되어 있을 것을 요구하기 때문입니다. 파일에서 더 아래에 나오는 함수를 호출하면 컴파일러가 암묵적 선언에 대해 불평합니다. 일반적인 해법은 파일 위쪽에 프로토타입을 두는 것이며, 그러면 정의를 원하는 순서대로 배치할 수 있습니다. 함수가 자기 자신을 호출하는 것도 적법하고 유용한데, 그것이 재귀입니다.

main도 함수다

main은 런타임이 대신 호출해 주는 평범한 함수입니다. 반환 타입이 int인 이유는 그 값이 프로그램의 종료 상태이기 때문입니다. 0은 성공, 0이 아닌 값은 실패를 뜻하며, 셸 스크립트와 빌드 도구가 이를 읽습니다.

int main(void) {
    /* ... */
    return 0;
}

C99부터는 main의 끝에 return 없이 도달해도 암묵적으로 0을 반환합니다. 이는 main에만 적용되는 특별 규칙입니다. 값을 반환하지 않고 끝나는 다른 모든 비-void 함수는, 호출한 쪽이 그 결과를 사용할 때 미정의 동작을 일으킵니다.

또 다른 표준 형태는 명령줄 인자를 받는 int main(int argc, char *argv[])입니다. 둘 다 올바릅니다. 오래된 튜토리얼이 뭐라고 하든 void main()은 올바르지 않습니다.

흔한 실수

  • void가 아닌 함수에서 return을 빠뜨리기. 그러면 호출한 쪽이 쓰레기 값을 읽습니다. gcc -Wall이 경고해 줍니다.
  • 본문 안에서 매개변수와 같은 이름의 변수를 선언하기. 매개변수를 가려버려서, 입력에 하려던 대입이 아무 데도 가지 않습니다.
  • 함수가 인자를 바꿔줄 것이라고 기대하기. C는 모든 것을 값으로 전달하므로 함수는 복사본을 받습니다. void reset(int x) { x = 0; }은 호출한 쪽에서 아무것도 바꾸지 않습니다. 이것이 다음 페이지의 주제입니다.
  • 호출에서 괄호를 빠뜨리기. printBanner;는 함수의 주소를 계산하고 버리는 적법한 표현식입니다. 컴파일은 되고, 아무 일도 하지 않으며, -Wall이 지적합니다.
  • (void) 대신 ()를 쓰기. 적법하지만, 예전 C 방언에서는 인자 검사를 꺼버립니다.

자주 묻는 질문

C에서 함수는 어떻게 선언하나요?

반환 타입, 이름, 괄호로 감싼 매개변수 목록을 쓰고 중괄호 안에 본문을 넣습니다: int add(int a, int b) { return a + b; }. 아무것도 받지 않는 함수는 (void)를 쓰고, 아무것도 반환하지 않는 함수는 반환 타입이 void입니다.

C 함수에서 return은 무슨 일을 하나요?

함수를 즉시 끝내고 호출한 쪽에 값을 넘겨줍니다. 값의 타입은 함수에 선언된 반환 타입과 맞아야 합니다. void 함수에서는 값 없이 return;만 써서 일찍 빠져나올 수 있고, 아예 생략하고 닫는 중괄호에서 함수가 끝나게 둘 수도 있습니다.

C에서 main은 왜 int를 반환하나요?

main의 반환값은 프로그램의 종료 상태(exit status)이며, 운영체제와 셸 스크립트가 이 값을 읽을 수 있습니다. return 0;은 성공을 뜻하고 0이 아닌 값은 실패를 뜻합니다. C99부터는 main의 끝에 return 문 없이 도달하면 암묵적으로 0을 반환합니다.

C 함수가 값을 두 개 이상 반환할 수 있나요?

직접적으로는 불가능합니다. return은 정확히 하나의 값만 만들어냅니다. 보통은 여러 필드를 담은 구조체를 반환하거나, 포인터를 매개변수로 넘겨서 함수가 그 포인터를 통해 추가 결과를 써 넣게 합니다.

Coddy programming languages illustration

Coddy로 코딩 배우기

시작하기