← C 강의 목록으로
🔥
심화 (Deep)
malloc · free · calloc · realloc

2단원 — 동적 메모리

지금까지 배열의 크기는 **컴파일 시점에 결정**됐습니다. 하지만 런타임에 입력으로 받은 N의 크기로 배열이 필요하다면? **동적 메모리 할당**의 차례입니다.

mallocfreerealloc
소요 시간
1~2시간
난이도
📊 고급
선수 조건
🎯 심화 1단원
결과물
런타임에 메모리를 직접 관리한다

이 강의에서 배우는 것

  • 1스택과 힙의 차이를 이해한다.
  • 2`malloc`, `calloc`, `realloc`, `free`의 역할을 안다.
  • 3메모리 누수, 이중 해제, 댕글링 포인터를 피한다.
  • 4동적 1차원/2차원 배열을 만든다.

왜 동적 메모리가 필요한가? — 스택과 힙

text
   메모리 영역
   ┌─────────────────────┐  ◄── 높은 주소
   │      스택 ▼          │  지역 변수, 함수 호출 정보
   │                     │  자동 할당/해제, 빠름, 작음(보통 1~8MB)
   ├─────────────────────┤
   │     ↕ (빈 공간)      │
   ├─────────────────────┤
   │       힙 ▲           │  malloc/free로 직접 관리
   │                     │  크기 제한이 거의 없음
   ├─────────────────────┤
   │  데이터/코드 영역    │
   └─────────────────────┘  ◄── 낮은 주소

스택만으로는:

  • 크기를 **컴파일 시 알아야** 함
  • 크기 제한 (큰 배열은 스택 오버플로)
  • 함수 종료 시 자동 사라짐 (함수 밖으로 들고 나갈 수 없음)

힙은 위 제약이 없으므로 **N개 입력 받아 N개 슬롯**, **함수가 만들어 반환** 같은 패턴이 가능해집니다.

핵심 개념

1) 4가지 함수 (`<stdlib.h>`)

함수의미
`void *malloc(size_t n)`n 바이트 할당 (초기화 X)
`void *calloc(size_t cnt, size_t sz)``cnt*sz` 바이트 할당 후 0으로 초기화
`void *realloc(void *p, size_t n)`기존 블록을 n 바이트로 재할당
`void free(void *p)`해제

2) 기본 사용 패턴 — "할당, 검사, 사용, 해제"

c
int *arr = malloc(n * sizeof(int));   // 1) 할당
if (arr == NULL) { /* 실패 처리 */ }   // 2) 검사

for (int i = 0; i < n; i++) arr[i] = i;   // 3) 사용

free(arr);                             // 4) 해제
arr = NULL;                            // (선택) 댕글링 방지

`malloc` 호출 1회 = `free` 호출 1회. **항상 짝**.

3) `realloc`의 안전한 사용

c
int *tmp = realloc(arr, new_size * sizeof(int));
if (tmp == NULL) {
    /* 실패: arr는 여전히 유효, 그대로 사용 가능 */
} else {
    arr = tmp;
}

결과를 **임시 변수**로 받아야 실패 시 원본을 잃지 않습니다.

4) 동적 2차원 배열

c
int **mat = malloc(rows * sizeof(int *));
for (int i = 0; i < rows; i++)
    mat[i] = malloc(cols * sizeof(int));

해제는 **역순**:

c
for (int i = 0; i < rows; i++) free(mat[i]);
free(mat);

예제로 보기

예제 1 — `ex01_malloc.c` : 동적 배열

c
int *arr = malloc((size_t)n * sizeof(int));
if (arr == NULL) { perror("malloc"); return 1; }

**입력**: `5`

text
배열 크기 입력: 10 20 30 40 50

핵심: 입력값 N에 따라 배열 크기가 결정됩니다. 정적 배열로는 불가능.

예제 2 — `ex02_calloc.c` : 0 초기화

**실행 결과**

text
calloc 직후: 0 0 0 0 0

핵심: `malloc`은 쓰레기 값, `calloc`은 0으로 초기화. 0 초기화가 필요하면 calloc.

예제 3 — `ex03_realloc.c` : 동적 확장

10개를 추가하면서 가득 차면 용량을 두 배로 확장.

**실행 결과**

text
(용량 확장: 8)
(용량 확장: 16)
최종 배열: 100 200 300 400 500 600 700 800 900 1000

핵심: 동적 자료구조(가변 길이 벡터, 큐 등)의 핵심 패턴.

예제 4 — `ex04_2d.c` : 동적 2차원 배열

**실행 결과**

text
   1   2   3   4
   5   6   7   8
   9  10  11  12

핵심: `int **`는 "포인터의 포인터". 두 번 할당, 두 번 해제.

다른 시각으로 보기 — "도서관 책 빌리기"

text
malloc = 도서관에서 책을 빌려 오기  (필요할 때 가서 빌림)
free   = 빌린 책을 반납하기         (다 쓰면 돌려줌)

빌리고 안 돌려주면      → 메모리 누수 (leak)
같은 책 두 번 반납      → 이중 해제 (double free)
빌렸던 책을 반납 후 또 봄 → 댕글링 포인터 (use-after-free)

도서관(OS)은 책을 무한정 가지고 있지 않으므로 **누수가 쌓이면** 결국 빌릴 책이 떨어집니다.

자주 하는 실수

  1. **누수(leak)**: `free` 호출을 잊거나 분기에서 빠뜨림.
  2. **이중 해제**: 같은 포인터를 두 번 free → 정의되지 않은 동작.
  3. **해제 후 사용**: 댕글링 포인터.
  4. **크기 계산 실수**: `malloc(n)` 처럼 `* sizeof(int)`를 빠뜨림.
  5. **`malloc` 결과 NULL 미검사**: 메모리가 부족하면 NULL 반환 → 그대로 쓰면 세그폴트.
  6. **`realloc` 결과를 같은 포인터로 받기**: 실패 시 원본까지 잃음.

정리

  • 스택은 자동·빠름·작음, 힙은 수동·유연·큼.
  • `malloc`/`calloc`/`realloc`로 할당, `free`로 해제.
  • 할당과 해제는 항상 **1:1로 짝**을 맞춘다.
  • `realloc`의 결과는 임시 변수로 받아 실패에 대비한다.
  • 누수·이중 해제·댕글링은 메모리 사고의 3대장.

직접 해 보기

bash
cd src
gcc -std=c11 -Wall -o ex01 ex01_malloc.c  && echo 5 | ./ex01
gcc -std=c11 -Wall -o ex02 ex02_calloc.c  && ./ex02
gcc -std=c11 -Wall -o ex03 ex03_realloc.c && ./ex03
gcc -std=c11 -Wall -o ex04 ex04_2d.c      && ./ex04

응용:

  • `valgrind ./ex01` 로 누수가 없는지 확인 (Linux).
  • `ex04`의 행/열을 실행 시간에 입력받도록 바꿔 보세요.

마무리

[메인 README](../../README.md) — 전체 커리큘럼 보기.

💻 예제 (examples)

실제로 컴파일·실행해 결과를 확인할 수 있는 예제입니다.

ex01_malloc.c동적 배열
CODE
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int n;
    printf("배열 크기 입력: ");
    scanf("%d", &n);

    int *arr = malloc((size_t)n * sizeof(int));
    if (arr == NULL) { perror("malloc"); return 1; }

    for (int i = 0; i < n; i++) arr[i] = (i + 1) * 10;

    for (int i = 0; i < n; i++) printf("%d ", arr[i]);
    putchar('\n');

    free(arr);
    return 0;
}
ex02_calloc.c0 초기화
CODE
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    size_t n = 5;
    int *arr = calloc(n, sizeof(int));   /* 0으로 초기화 */
    if (!arr) { perror("calloc"); return 1; }

    printf("calloc 직후: ");
    for (size_t i = 0; i < n; i++) printf("%d ", arr[i]);
    putchar('\n');

    free(arr);
    return 0;
}
▶ 실행 결과
calloc 직후: 0 0 0 0 0
ex03_realloc.c동적 확장
CODE
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    size_t cap = 4;
    size_t len = 0;
    int *arr = malloc(cap * sizeof(int));
    if (!arr) { perror("malloc"); return 1; }

    /* 10개를 추가하면서 필요할 때 확장 */
    for (int i = 0; i < 10; i++) {
        if (len == cap) {
            cap *= 2;
            int *tmp = realloc(arr, cap * sizeof(int));
            if (!tmp) { free(arr); perror("realloc"); return 1; }
            arr = tmp;
            printf("(용량 확장: %zu)\n", cap);
        }
        arr[len++] = (i + 1) * 100;
    }

    printf("최종 배열: ");
    for (size_t i = 0; i < len; i++) printf("%d ", arr[i]);
    putchar('\n');

    free(arr);
    return 0;
}
▶ 실행 결과
(용량 확장: 8)
(용량 확장: 16)
최종 배열: 100 200 300 400 500 600 700 800 900 1000
ex04_2d.c동적 2차원 배열
CODE
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int rows = 3, cols = 4;

    /* 행 포인터 배열 할당 */
    int **mat = malloc((size_t)rows * sizeof(int *));
    if (!mat) { perror("malloc"); return 1; }

    /* 각 행 할당 */
    for (int i = 0; i < rows; i++) {
        mat[i] = malloc((size_t)cols * sizeof(int));
        if (!mat[i]) { perror("malloc"); return 1; }
        for (int j = 0; j < cols; j++) mat[i][j] = i * cols + j + 1;
    }

    /* 출력 */
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) printf("%4d", mat[i][j]);
        putchar('\n');
    }

    /* 역순 해제 */
    for (int i = 0; i < rows; i++) free(mat[i]);
    free(mat);
    return 0;
}
▶ 실행 결과
   1   2   3   4
   5   6   7   8
   9  10  11  12

📝 과제 (exercises)

직접 풀어보고, 막힐 때 정답을 펼쳐 비교해보세요.

과제 1

문제 1 (hw01.c)

목표: 사용자에게 배열 크기 N을 입력받고, N개의 정수를 입력받은 뒤 **최댓값과 최솟값**을 출력하세요. 메모리는 `malloc`/`free`로 관리.

요구사항
  • 파일명: hw01.c
정답 코드 펼치기 / 접기
SOLUTION
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int n;
    printf("N 입력: ");
    scanf("%d", &n);
    if (n <= 0) { printf("N은 양수여야 합니다.\n"); return 1; }

    int *arr = malloc((size_t)n * sizeof(int));
    if (!arr) { perror("malloc"); return 1; }

    printf("정수 %d개 입력: ", n);
    for (int i = 0; i < n; i++) scanf("%d", &arr[i]);

    int mx = arr[0], mn = arr[0];
    for (int i = 1; i < n; i++) {
        if (arr[i] > mx) mx = arr[i];
        if (arr[i] < mn) mn = arr[i];
    }
    printf("최댓값: %d\n최솟값: %d\n", mx, mn);

    free(arr);
    return 0;
}
과제 2

문제 2 (hw02.c)

목표: 임의의 N×M 행렬을 동적 할당해 0으로 초기화한 뒤, 대각선만 1로 바꾸어 출력하세요. (단위행렬 비슷한 효과, N=M일 필요는 없음)

요구사항
  • 파일명: hw02.c
정답 코드 펼치기 / 접기
SOLUTION
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int n, m;
    printf("행 N, 열 M: ");
    scanf("%d %d", &n, &m);

    int **mat = calloc((size_t)n, sizeof(int *));
    if (!mat) { perror("calloc"); return 1; }
    for (int i = 0; i < n; i++) {
        mat[i] = calloc((size_t)m, sizeof(int));
        if (!mat[i]) { perror("calloc"); return 1; }
    }

    int len = (n < m) ? n : m;
    for (int i = 0; i < len; i++) mat[i][i] = 1;

    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) printf("%2d", mat[i][j]);
        putchar('\n');
    }

    for (int i = 0; i < n; i++) free(mat[i]);
    free(mat);
    return 0;
}
과제 3

문제 3 (hw03.c)

목표: 사용자에게 문자열을 입력받아 **그 문자열의 복사본**을 동적으로 할당해 반환하는 `char *my_strdup(const char *s)` 함수를 구현하고 사용 예를 보이세요. (`<string.h>`의 `strlen`/`memcpy` 사용 가능)

요구사항
  • 파일명: hw03.c
정답 코드 펼치기 / 접기
SOLUTION
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char *my_strdup(const char *s) {
    size_t len = strlen(s);
    char *copy = malloc(len + 1);
    if (!copy) return NULL;
    memcpy(copy, s, len + 1);   /* 널 문자 포함 */
    return copy;
}

int main(void) {
    char buf[256];
    printf("문장 입력: ");
    if (fgets(buf, sizeof(buf), stdin) == NULL) return 1;
    buf[strcspn(buf, "\n")] = '\0';

    char *dup = my_strdup(buf);
    if (!dup) { perror("malloc"); return 1; }

    printf("원본:  %s\n", buf);
    printf("복사본: %s\n", dup);

    free(dup);
    return 0;
}
예제 코드 / 강의 자료

전체 강의 자료와 예제 코드는 GitHub에서 자유롭게 받아볼 수 있습니다.

GitHub에서 보기 ↗