# C언어 (3)

## 1\. 함수(Function)

### 1-1. 함수란?

함수는 **특정 작업을 수행하는 코드 묶음** 이다. 반복되는 코드를 하나로 묶어 이름을 붙이고, 필요할 때마다 호출해서 사용한다.

JavaScript에서 `function`으로 함수를 만들어 쓰는 것과 같은 개념이다. 다만 C에서는 반환 타입을 명시해야 하고, 매개변수에도 타입을 지정해야 한다는 차이가 있다.

함수를 사용하는 이유는 크게 세 가지다.

**코드 재사용** — 같은 로직을 여러 번 작성할 필요 없이, 한 번 정의하고 호출만 하면 된다. **가독성** — 복잡한 프로그램을 작은 단위로 쪼개면 코드를 읽고 이해하기 쉬워진다. **유지보수** — 로직을 수정할 때 함수 하나만 고치면 호출하는 곳 전체에 반영된다.

### 1-2. 함수의 형태

C언어의 함수는 **반환타입**, **함수이름**, **매개변수**, **함수 본체** 로 구성된다.

```c
반환타입 함수이름(매개변수) {
    // 함수 본체
    return 반환값;
}
```

실제 예시를 보면 이렇다.

```c
int add(int a, int b) {
    return a + b;
}
```

`int` 는 이 함수가 정수를 반환한다는 뜻이고, `add` 는 함수 이름이다. `int a, int b` 는 매개변수로, 함수를 호출할 때 전달받는 값이다. `return a + b` 로 결과를 돌려준다.

반환할 값이 없으면 반환타입을 `void` 로 지정한다. 이 경우 `return` 은 생략하거나 `return;` 만 쓴다.

```c
void printHello(void) {
    printf("Hello!\n");
}
```

매개변수가 없을 때는 괄호 안에 `void` 를 쓰거나 비워둔다. C에서는 `void` 를 명시하는 것이 정확한 표현이다. 괄호를 비워두면 "매개변수가 정해지지 않았다"는 의미가 되어 의도와 다를 수 있다.

### 1-3. 함수의 사용방법

함수를 사용하려면 **선언(프로토타입)**, **정의(구현)**, **호출** 세 단계를 알아야 한다.

```c
#include <stdio.h>

int add(int a, int b);  // 함수 선언 (프로토타입)

int main(void) {
    int result = add(3, 5);  // 함수 호출
    printf("%d\n", result);  // 8
    return 0;
}

int add(int a, int b) {  // 함수 정의 (구현)
    return a + b;
}
```

**함수 선언(프로토타입)** 은 컴파일러에게 "이런 함수가 있을 것이다"라고 미리 알려주는 것이다. 반환타입, 함수이름, 매개변수 타입만 적고 세미콜론으로 끝낸다. `main` 보다 아래에 함수를 정의할 때 필요하다.

**함수 정의** 는 함수가 실제로 무슨 일을 하는지 구현하는 것이다.

**함수 호출** 은 정의된 함수를 실행하는 것이다. 함수 이름 뒤에 괄호를 쓰고 인자를 전달하면 된다.

만약 함수를 `main` 위에 정의하면 프로토타입 선언 없이도 사용할 수 있다.

```c
#include <stdio.h>

int add(int a, int b) {
    return a + b;
}

int main(void) {
    printf("%d\n", add(3, 5));
    return 0;
}
```

C에서 함수의 인자 전달은 **값에 의한 전달(call by value)** 이다. 함수에 변수를 넘기면 원본이 아니라 **복사본** 이 전달된다. 함수 안에서 매개변수 값을 바꿔도 원본 변수에는 영향이 없다.

```c
void change(int x) {
    x = 100;  // 복사본만 바뀜
}

int main(void) {
    int a = 5;
    change(a);
    printf("%d\n", a);  // 여전히 5
    return 0;
}
```

원본을 수정하려면 포인터를 사용해서 주소를 넘겨야 한다. 이건 포인터를 배울 때 다시 다루게 된다.

### 1-4. 함수의 범위

함수의 범위란 함수가 **어디서 호출될 수 있는지** 를 의미한다.

기본적으로 같은 소스 파일 안에서는 어디서든 함수를 호출할 수 있다. 단, 호출하는 시점에서 컴파일러가 그 함수의 존재를 알고 있어야 한다. 그래서 프로토타입 선언을 위에 적거나, 함수 정의 자체를 호출보다 위에 두는 것이다.

여러 소스 파일로 나뉜 프로젝트에서는 **헤더 파일(**`.h`**)** 에 함수 프로토타입을 모아두고, 각 소스 파일에서 `#include` 로 포함시켜 사용한다.

```c
// math_utils.h
int add(int a, int b);
int subtract(int a, int b);

// math_utils.c
#include "math_utils.h"

int add(int a, int b) {
    return a + b;
}

int subtract(int a, int b) {
    return a - b;
}

// main.c
#include <stdio.h>
#include "math_utils.h"

int main(void) {
    printf("%d\n", add(3, 5));
    printf("%d\n", subtract(10, 4));
    return 0;
}
```

함수 앞에 `static` 을 붙이면 해당 소스 파일 안에서만 사용할 수 있는 **내부 함수** 가 된다. 다른 파일에서는 호출할 수 없다.

```c
static int helper(int x) {
    return x * 2;
}
```

* * *

## 2\. 변수

변수는 선언되는 위치와 키워드에 따라 **생존 범위(scope)** 와 **생존 기간(lifetime)** 이 달라진다.

### 2-1. 지역변수 (Local Variable)

함수나 블록(`{}`) 안에서 선언된 변수다. 해당 블록 안에서만 사용할 수 있고, 블록이 끝나면 메모리에서 사라진다.

```c
void foo(void) {
    int x = 10;  // 지역변수
    printf("%d\n", x);
}  // 여기서 x는 소멸

int main(void) {
    foo();
    // printf("%d\n", x);  // 에러! x는 foo 안에서만 존재
    return 0;
}
```

지역변수는 **스택(stack) 메모리** 에 할당된다. 함수가 호출되면 생성되고, 함수가 끝나면 자동으로 해제된다. 초기화하지 않으면 **쓰레기값** 이 들어있다.

같은 이름의 지역변수가 서로 다른 함수에 있어도 완전히 별개의 변수다.

```c
void funcA(void) {
    int x = 10;
    printf("A: %d\n", x);  // 10
}

void funcB(void) {
    int x = 20;
    printf("B: %d\n", x);  // 20
}
```

`if`, `for`, `while` 같은 블록 안에서 선언한 변수도 지역변수다. 해당 블록이 끝나면 사라진다.

```c
for (int i = 0; i < 5; i++) {
    int temp = i * 2;  // 매 반복마다 생성/소멸
}
// i와 temp 모두 여기서 사용 불가
```

### 2-2. 전역변수 (Global Variable)

함수 바깥에서 선언된 변수다. 프로그램이 시작될 때 생성되고, 프로그램이 종료될 때까지 유지된다. 모든 함수에서 접근할 수 있다.

```c
#include <stdio.h>

int count = 0;  // 전역변수

void increment(void) {
    count++;
}

void printCount(void) {
    printf("count: %d\n", count);
}

int main(void) {
    increment();
    increment();
    increment();
    printCount();  // count: 3
    return 0;
}
```

전역변수는 **데이터 영역(data segment)** 에 할당된다. 초기화하지 않으면 지역변수와 달리 **자동으로 0으로 초기화** 된다.

편리해 보이지만 전역변수의 남용은 위험하다. 어디서든 값을 바꿀 수 있기 때문에, 프로그램이 커지면 어떤 함수가 값을 변경했는지 추적하기 어려워진다. 버그의 원인이 되기 쉽고 유지보수도 힘들어진다. 꼭 필요한 경우가 아니라면 지역변수를 사용하고, 함수 간 데이터 전달은 매개변수와 반환값으로 처리하는 것이 좋다.

전역변수와 지역변수의 이름이 같으면 **지역변수가 우선** 한다.

```c
int x = 100;  // 전역변수

void foo(void) {
    int x = 5;  // 지역변수가 전역변수를 가림
    printf("%d\n", x);  // 5
}
```

### 2-3. 정적변수 (Static Variable)

지역변수에 `static` 키워드를 붙이면 정적변수가 된다. 지역변수처럼 **해당 함수 안에서만 접근** 할 수 있지만, 함수가 끝나도 **값이 사라지지 않고 유지** 된다.

```c
void counter(void) {
    static int count = 0;  // 최초 호출 때만 초기화됨
    count++;
    printf("호출 횟수: %d\n", count);
}

int main(void) {
    counter();  // 호출 횟수: 1
    counter();  // 호출 횟수: 2
    counter();  // 호출 횟수: 3
    return 0;
}
```

일반 지역변수였다면 함수가 호출될 때마다 `count`가 0으로 초기화되어 항상 1이 출력될 것이다. 하지만 `static` 으로 선언했기 때문에 초기화는 처음 한 번만 실행되고, 이후에는 이전 값이 그대로 유지된다.

정적변수는 전역변수와 마찬가지로 **데이터 영역** 에 저장된다. 초기화하지 않으면 자동으로 0이 된다. 다만 접근 범위는 선언된 함수 안으로 제한되므로, 전역변수의 단점(어디서든 접근 가능)을 피하면서도 값을 유지할 수 있다.

정리하면 이렇다.

**지역변수** 는 함수 안에서만 접근 가능하고, 함수가 끝나면 사라진다. **전역변수** 는 어디서든 접근 가능하고, 프로그램이 끝날 때까지 유지된다. **정적변수** 는 함수 안에서만 접근 가능하지만, 프로그램이 끝날 때까지 값이 유지된다. 지역변수의 스코프와 전역변수의 수명을 합쳐놓은 것이라고 보면 된다.

* * *

## 마무리

함수는 코드를 구조화하는 가장 기본적인 단위이고, 변수의 범위는 프로그램의 데이터 흐름을 결정한다. 함수와 변수의 스코프를 정확히 이해하면, 코드가 길어져도 데이터가 어디에 있고 어디까지 영향을 미치는지 파악할 수 있다. 이 개념은 이후 포인터와 동적 메모리 할당을 배울 때 더욱 중요해진다.
