프로그램이 커지면 같은 코드가 여기저기 반복되고, 전체를 한눈에 파악하기 어려워진다. 함수는 특정 기능을 하나의 덩어리로 묶어 이름을 붙인 것으로, 이 문제를 푸는 핵심 도구다. 우리가 이미 써 온 printf, scanf도 모두 함수였다.
1. 함수란?
특정 작업을 수행하는 명령어들의 묶음으로, 고유한 이름을 가지며 입력(인수)을 받아 결과(반환값)를 돌려줄 수 있다.
함수를 쓰면 좋은 점은 분명했다.
- 중복 제거: 같은 코드를 반복하지 않고 한 번 정의해 여러 번 호출한다.
- 재사용: 한 번 잘 만든 함수는 다른 프로그램에서도 쓸 수 있다.
- 모듈화: 프로그램을 기능 단위로 나눠, 개발과 유지보수가 쉬워진다.
함수는 크게 우리가 직접 만드는 사용자 정의 함수와, 표준 라이브러리가 제공하는 라이브러리 함수(printf, sqrt 등)로 나뉜다.
2. 함수의 정의

함수는 반환형, 이름, 매개변수, 몸체로 이뤄진다.
void print_stars(void) // 반환형 void / 이름 / 매개변수 없음
{
for (int i = 0; i < 30; i++)
printf("*");
}
- 반환형: 함수가 일을 마치고 돌려주는 값의 자료형. 돌려줄 값이 없으면
void. - 함수 이름: 식별자 규칙을 따르되, 기능을 드러내는 이름(동사+명사)이 좋다.
- 함수 몸체: 중괄호 안의 실제 수행 문장들.
3. 함수 원형 (prototype)
C는 코드를 위에서 아래로 읽으므로, main보다 아래에 정의된 함수를 main에서 호출하면 컴파일러가 그 함수를 아직 모른다. 그래서 함수 원형으로 미리 알려준다.
int add(int x, int y); // 함수 원형: 이름·매개변수 타입·반환형만 미리 선언
int main(void) {
printf("%d\n", add(3, 4)); // 정의는 아래에 있어도 호출 가능
return 0;
}
int add(int x, int y) { // 실제 정의
return x + y;
}
원형은 함수 헤더에 세미콜론만 붙인 형태다. 매개변수는 타입만 적어도 되지만, 이름까지 적어두면 읽기 좋다. 함수를 main 아래에 정의했다가 컴파일 경고를 만난 적이 있는데, 원형을 위에 선언하니 깔끔히 풀렸다. C가 위에서 아래로 읽는다는 성질이 여기서 체감됐다.
4. 호출과 반환, 인수와 매개변수

함수를 호출(call)하면 현재 실행이 잠시 멈추고 그 함수로 넘어간다. 함수가 끝나거나 return을 만나면, 호출한 자리로 되돌아와 멈췄던 곳부터 다시 진행한다.
여기서 자주 헷갈리는 두 용어를 구분했다.
- 인수(argument): 호출하는 쪽에서 실제로 넘기는 값 (
add(3, 4)의 3, 4). - 매개변수(parameter): 그 값을 받는 변수 (
int add(int x, int y)의 x, y).
함수에 넘기는 인수는 여러 개일 수 있지만, return으로 돌려주는 값은 단 하나다. 그리고 인수의 개수와 타입은 매개변수와 정확히 일치해야 한다. 여러 결과를 돌려주고 싶다면 포인터나 구조체 같은 방법이 따로 필요하다.
int max(int x, int y) {
if (x > y) return x;
else return y;
}
5. 수업 코드로 보는 소수 판별 모듈화
prime_no_prg.c는 "입력 받기"와 "소수 판별하기"를 함수로 나누는 예제였다. main은 전체 흐름만 담당하고, 실제 세부 작업은 함수가 맡는다.
소수 판별 함수 내부는 약수 개수를 세는 반복으로 볼 수 있다.
int getIntNo(void);
void printPrimeResult(int iNo);
int main(void)
{
int iNo;
while (1)
{
iNo = getIntNo();
if (iNo == 0)
{
printf("[EXIT]\n");
break;
}
printPrimeResult(iNo);
}
return 0;
}
int getIntNo(void)
{
int iNo;
printf("정수 입력(0 종료): ");
scanf("%d", &iNo);
return iNo;
}
void printPrimeResult(int iNo)
{
int iIndex;
int iDivisor;
int iCount;
iCount = 0;
for (iIndex = 0; iIndex < iNo; iIndex++)
{
iDivisor = iIndex + 1;
if (iNo % iDivisor == 0)
{
iCount = iCount + 1;
}
}
if (iCount == 2)
{
printf("%d는 소수입니다.\n", iNo);
}
else
{
printf("%d는 소수가 아닙니다.\n", iNo);
}
}
이 예제의 핵심은 main이 모든 계산을 직접 하지 않는다는 점이다. main은 입력 → 종료 판단 → 함수 호출 흐름만 갖고, 소수 판별의 세부 반복은 printPrimeResult 안으로 들어간다.
오늘 느낀 점
- 함수가 단순히 코드를 줄이는 게 아니라 "기능 단위로 나눠 생각하는" 도구라는 걸 알았다. 중복 제거보다 모듈화가 더 큰 가치였다.
- 인수와 매개변수를 "넘기는 값 vs 받는 변수"로 구분하니, 그동안 뭉뚱그려 쓰던 용어가 또렷해졌다.
- return이 값을 돌려주면서 동시에 함수를 끝낸다는 점이, 조건마다 return을 둬 코드를 평평하게 만드는 방식으로 이어진다는 게 흥미로웠다.
한 걸음 더
- 함수에 값을 넘길 때 C는 기본적으로 값에 의한 호출(call by value)을 한다. 인수의 "값을 복사해서" 매개변수에 전달하므로, 함수 안에서 매개변수를 바꿔도 호출한 쪽의 원본 변수는 변하지 않는다. 원본을 바꾸고 싶으면 주소를 넘기는 포인터가 필요한데, 이게 포인터를 배우는 큰 동기가 된다.
- 함수 안에서 선언한 변수(지역 변수)는 그 함수가 실행되는 동안만 존재하고 끝나면 사라진다. 그래서 다른 함수에서 같은 이름의 변수를 써도 서로 간섭하지 않는다. 이 "각 함수가 자기만의 작업 공간을 갖는다"는 성질이 모듈화의 토대다.
return은 값을 돌려줄 뿐 아니라 함수를 즉시 끝내는 효과도 있다. 그래서max예처럼 조건마다 return을 두면, 굳이 else 없이도 "조건에 맞으면 그 값을 돌려주고 함수 종료"가 된다. 이를 활용하면 깊은 if 중첩을 평평하게 펼 수 있다.
'language > c' 카테고리의 다른 글
| [C언어] 05. 반복문 (0) | 2026.08.03 |
|---|---|
| [C언어] 04. 조건문 (0) | 2026.07.27 |
| [C언어] 03. 수식과 연산자 (0) | 2026.07.20 |
| [C언어] 02. 기본 변수와 입력 (0) | 2026.07.10 |
| [C언어] 01. 첫 프로젝트 (0) | 2026.06.28 |