1. 동적 메모리 할당이란?
동적 메모리 할당은 프로그램 실행 중에 필요한 만큼 메모리를 할당하는 기법입니다.
정적 메모리 vs 동적 메모리
| 구분 | 정적 메모리 | 동적 메모리 |
|---|---|---|
| 할당 영역 | 데이터/스택 영역 | 힙 영역 |
| 할당 시점 | 컴파일 타임 | 런타임 |
| 크기 결정 | 선언 시 고정 | 실행 중 결정 |
| 생명주기 | 자동 관리 | 수동 관리 |
| 예시 | int arr[100]; |
malloc(100); |
동적 메모리의 장점
• 필요한 만큼만 메모리 사용 (효율적)
• 실행 중 크기 변경 가능
• 큰 데이터 구조 생성 가능
• 필요한 만큼만 메모리 사용 (효율적)
• 실행 중 크기 변경 가능
• 큰 데이터 구조 생성 가능
동적 메모리가 필요한 이유
다음 코드는 문제가 발생할 수 있습니다.
#include <stdio.h>
#include <string.h>
char *createString(void) {
char str[100]; // 지역 변수 (스택 영역)
printf("문자열 입력: ");
scanf("%s", str);
return str; // ⚠️ 위험! 함수 종료 후 str은 소멸됨
}
int main(void) {
char *result = createString();
printf("결과: %s\n", result); // 예측 불가능한 동작
return 0;
}
⚠️ 문제점
지역 변수
반환된 주소는 이미 해제된 메모리를 가리키므로 위험합니다!
지역 변수
str은 함수가 끝나면 스택에서 사라집니다.반환된 주소는 이미 해제된 메모리를 가리키므로 위험합니다!
해결책: 동적 메모리 할당
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char *createString(void) {
char *str = (char *)malloc(100); // 힙 영역에 할당
if (str == NULL) {
printf("메모리 할당 실패\n");
return NULL;
}
printf("문자열 입력: ");
scanf("%s", str);
return str; // ✅ 안전! 힙 메모리는 유지됨
}
int main(void) {
char *result = createString();
if (result != NULL) {
printf("결과: %s\n", result);
free(result); // 사용 후 메모리 해제
}
return 0;
}
2. malloc 함수
malloc 함수는 지정한 크기만큼 힙 메모리를 할당합니다.
malloc 함수의 사용법
#include <stdlib.h>
void *malloc(size_t size);
- 매개변수: 할당할 바이트 수
- 반환값: 할당된 메모리의 주소 (void *)
- 실패 시: NULL 반환
💡 malloc 사용 패턴
자료형 *포인터 = (자료형 *)malloc(sizeof(자료형) * 개수);
기본 사용 예제
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *ptr;
// int 하나 크기만큼 메모리 할당
ptr = (int *)malloc(sizeof(int));
if (ptr == NULL) {
printf("메모리 할당 실패\n");
return 1;
}
*ptr = 100;
printf("값: %d\n", *ptr);
free(ptr); // 메모리 해제
return 0;
}
실행 결과 보기
값: 100
배열처럼 사용하기
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *arr;
int i;
// int 5개 크기만큼 메모리 할당
arr = (int *)malloc(sizeof(int) * 5);
if (arr == NULL) {
printf("메모리 할당 실패\n");
return 1;
}
// 배열처럼 사용
for (i = 0; i < 5; i++) {
arr[i] = (i + 1) * 10;
}
printf("할당된 배열: ");
for (i = 0; i < 5; i++) {
printf("%d ", arr[i]);
}
printf("\n");
free(arr); // 메모리 해제
return 0;
}
실행 결과 보기
할당된 배열: 10 20 30 40 50
malloc의 특징
- 할당된 메모리는 초기화되지 않음 (쓰레기 값 포함)
- 반드시 형 변환 필요:
(자료형 *) - NULL 체크 필수
3. calloc 함수
calloc 함수는 malloc과 유사하지만 메모리를 0으로 초기화합니다.
calloc 함수의 사용법
#include <stdlib.h>
void *calloc(size_t count, size_t size);
- 매개변수 1: 요소 개수
- 매개변수 2: 각 요소의 크기
- 반환값: 할당된 메모리의 주소 (void *)
malloc vs calloc 비교
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *arr1, *arr2;
int i;
// malloc: 초기화 안 됨
arr1 = (int *)malloc(sizeof(int) * 5);
// calloc: 0으로 초기화됨
arr2 = (int *)calloc(5, sizeof(int));
printf("malloc 결과: ");
for (i = 0; i < 5; i++) {
printf("%d ", arr1[i]); // 쓰레기 값
}
printf("\ncalloc 결과: ");
for (i = 0; i < 5; i++) {
printf("%d ", arr2[i]); // 모두 0
}
printf("\n");
free(arr1);
free(arr2);
return 0;
}
실행 결과 보기 (예시)
malloc 결과: -858993460 -858993460 -858993460 -858993460 -858993460 calloc 결과: 0 0 0 0 0
malloc은 쓰레기 값, calloc은 0으로 초기화됩니다.
calloc의 장점
• 자동으로 0 초기화
• 배열 생성 시 안전
• malloc보다 약간 느릴 수 있음
• 자동으로 0 초기화
• 배열 생성 시 안전
• malloc보다 약간 느릴 수 있음
4. realloc 함수
realloc 함수는 이미 할당된 메모리 크기를 변경합니다.
realloc 함수의 사용법
#include <stdlib.h>
void *realloc(void *ptr, size_t new_size);
- 매개변수 1: 기존 메모리 주소
- 매개변수 2: 새로운 크기 (바이트)
- 반환값: 재할당된 메모리 주소
realloc 동작 방식
- 기존 위치에 충분한 공간이 있으면 → 같은 주소 반환
- 공간이 부족하면 → 새 위치에 할당 후 데이터 복사, 새 주소 반환
기본 사용 예제
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *arr;
int i;
// 초기 할당: int 3개
arr = (int *)calloc(3, sizeof(int));
arr[0] = 10;
arr[1] = 20;
arr[2] = 30;
printf("초기 배열: ");
for (i = 0; i < 3; i++) {
printf("%d ", arr[i]);
}
printf("\n");
// 크기 확장: int 5개로
arr = (int *)realloc(arr, sizeof(int) * 5);
if (arr == NULL) {
printf("재할당 실패\n");
return 1;
}
arr[3] = 40;
arr[4] = 50;
printf("확장 후 배열: ");
for (i = 0; i < 5; i++) {
printf("%d ", arr[i]);
}
printf("\n");
free(arr);
return 0;
}
실행 결과 보기
초기 배열: 10 20 30 확장 후 배열: 10 20 30 40 50
동적 배열 구현
실행 중에 크기를 조절하는 배열을 만들 수 있습니다.
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *arr = NULL;
int size = 0;
int capacity = 2;
int input;
arr = (int *)calloc(capacity, sizeof(int));
printf("정수를 입력하세요 (-1 종료):\n");
while (1) {
scanf("%d", &input);
if (input == -1) break;
// 용량 초과 시 2배로 확장
if (size == capacity) {
capacity *= 2;
arr = (int *)realloc(arr, sizeof(int) * capacity);
printf("[배열 확장: %d개]\n", capacity);
}
arr[size++] = input;
}
printf("\n입력한 숫자: ");
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
free(arr);
return 0;
}
실행 결과 보기 (예시)
정수를 입력하세요 (-1 종료): 10 20 30 [배열 확장: 4개] 40 50 [배열 확장: 8개] -1 입력한 숫자: 10 20 30 40 50
⚠️ realloc 주의사항
• 실패 시 NULL 반환, 기존 메모리는 유지됨
•
• 임시 포인터 사용 권장:
• 실패 시 NULL 반환, 기존 메모리는 유지됨
•
arr = realloc(arr, ...);는 위험! (실패 시 주소 손실)• 임시 포인터 사용 권장:
temp = realloc(arr, ...); if (temp) arr = temp;
5. free 함수
free 함수는 할당된 메모리를 해제합니다.
free 함수의 사용법
#include <stdlib.h>
void free(void *ptr);
- 매개변수: 해제할 메모리 주소
- 반환값: 없음
free의 중요성
#include <stdio.h>
#include <stdlib.h>
void memoryLeak(void) {
int *ptr = (int *)malloc(sizeof(int) * 1000000);
// free(ptr); 없음! → 메모리 누수
}
int main(void) {
int i;
for (i = 0; i < 10000; i++) {
memoryLeak(); // 계속 메모리 할당만 함
}
printf("프로그램 종료\n");
// 프로그램이 종료되어야 메모리 해제됨
return 0;
}
⚠️ 메모리 누수 (Memory Leak)
할당한 메모리를 해제하지 않으면:
• 프로그램이 사용하는 메모리 증가
• 시스템 성능 저하
• 최악의 경우 프로그램/시스템 다운
할당한 메모리를 해제하지 않으면:
• 프로그램이 사용하는 메모리 증가
• 시스템 성능 저하
• 최악의 경우 프로그램/시스템 다운
올바른 메모리 관리
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *ptr;
// 1. 메모리 할당
ptr = (int *)malloc(sizeof(int) * 5);
if (ptr == NULL) {
printf("할당 실패\n");
return 1;
}
// 2. 메모리 사용
for (int i = 0; i < 5; i++) {
ptr[i] = i * 10;
}
// 3. 메모리 해제
free(ptr);
// 4. 포인터 초기화 (선택사항, 권장)
ptr = NULL;
return 0;
}
메모리 관리 원칙
• malloc/calloc으로 할당 → free로 해제
• 할당과 해제는 1:1 대응
• 해제 후 포인터에 NULL 대입 권장
• 같은 메모리를 두 번 해제 금지
• malloc/calloc으로 할당 → free로 해제
• 할당과 해제는 1:1 대응
• 해제 후 포인터에 NULL 대입 권장
• 같은 메모리를 두 번 해제 금지
6. 메모리 관리 실전 예제
예제 1: 동적 문자열 처리
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char *getString(void) {
char temp[100];
char *str;
printf("문자열 입력: ");
scanf("%s", temp);
// 실제 길이만큼 메모리 할당
str = (char *)malloc(strlen(temp) + 1);
if (str != NULL) {
strcpy(str, temp);
}
return str;
}
int main(void) {
char *result = getString();
if (result != NULL) {
printf("입력한 문자열: %s\n", result);
printf("길이: %d 바이트\n", (int)strlen(result));
free(result);
}
return 0;
}
실행 결과 보기
문자열 입력: Programming 입력한 문자열: Programming 길이: 11 바이트
예제 2: 성적 관리 프로그램
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int n, i;
int *scores;
int sum = 0;
double avg;
printf("학생 수 입력: ");
scanf("%d", &n);
// 학생 수만큼 동적 할당
scores = (int *)calloc(n, sizeof(int));
if (scores == NULL) {
printf("메모리 할당 실패\n");
return 1;
}
// 성적 입력
for (i = 0; i < n; i++) {
printf("%d번 학생 점수: ", i + 1);
scanf("%d", &scores[i]);
sum += scores[i];
}
// 평균 계산
avg = (double)sum / n;
// 결과 출력
printf("\n=== 결과 ===\n");
printf("전체 점수: ");
for (i = 0; i < n; i++) {
printf("%d ", scores[i]);
}
printf("\n평균: %.2f점\n", avg);
free(scores);
return 0;
}
실행 결과 보기
학생 수 입력: 3 1번 학생 점수: 85 2번 학생 점수: 90 3번 학생 점수: 78 === 결과 === 전체 점수: 85 90 78 평균: 84.33점
예제 3: 2차원 배열 동적 할당
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int **matrix;
int rows = 3, cols = 4;
int i, j;
// 행 포인터 배열 할당
matrix = (int **)malloc(sizeof(int *) * rows);
// 각 행에 대한 열 할당
for (i = 0; i < rows; i++) {
matrix[i] = (int *)malloc(sizeof(int) * cols);
}
// 값 초기화
for (i = 0; i < rows; i++) {
for (j = 0; j < cols; j++) {
matrix[i][j] = i * cols + j + 1;
}
}
// 출력
printf("=== 행렬 출력 ===\n");
for (i = 0; i < rows; i++) {
for (j = 0; j < cols; j++) {
printf("%3d ", matrix[i][j]);
}
printf("\n");
}
// 메모리 해제 (역순)
for (i = 0; i < rows; i++) {
free(matrix[i]);
}
free(matrix);
return 0;
}
실행 결과 보기
=== 행렬 출력 === 1 2 3 4 5 6 7 8 9 10 11 12
7. 종합 실습
문제 1 - malloc 기초 (기초)
문제 1
다음 코드의 실행 결과는?
동적 메모리 할당
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *ptr = (int *)malloc(sizeof(int));
*ptr = 777;
printf("%d", *ptr);
free(ptr);
return 0;
}
문제 2 - calloc vs malloc (기초)
문제 2
calloc과 malloc의 차이점은?
A. calloc은 메모리를 0으로 초기화한다.
B. malloc은 더 빠르다.
C. calloc은 매개변수가 2개다.
D. 위의 모든 설명이 맞다.
동적 메모리 할당
문제 3 - 배열 크기 계산 (중급)
문제 3
다음 코드에서 할당되는 총 바이트 수는?
동적 메모리 할당
double은 8바이트, 10개이므로 8 × 10 = 80바이트
#include <stdlib.h>
int main(void) {
double *arr = (double *)malloc(sizeof(double) * 10);
// 총 몇 바이트?
free(arr);
return 0;
}
문제 4 - realloc 이해 (중급)
문제 4
다음 코드의 실행 결과는?
동적 메모리 할당
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *arr = (int *)calloc(3, sizeof(int));
arr[0] = 10;
arr[1] = 20;
arr[2] = 30;
arr = (int *)realloc(arr, sizeof(int) * 5);
arr[3] = 40;
arr[4] = 50;
printf("%d", arr[2] + arr[4]);
free(arr);
return 0;
}
문제 5 - 메모리 누수 (고급)
문제 5
다음 코드에서 메모리 누수가 발생하는 줄은?
동적 메모리 할당
9행에서 ptr1이 가리키던 메모리 주소를 잃어버려 해제할 수 없게 됩니다.
#include <stdlib.h>
int main(void) {
int *ptr1 = (int *)malloc(sizeof(int)); // 3행
int *ptr2 = (int *)malloc(sizeof(int)); // 4행
*ptr1 = 100;
*ptr2 = 200;
ptr1 = ptr2; // 9행
free(ptr1); // 11행
return 0;
}
문제 6 - 동적 할당 종합 (고급)
문제 6
다음 코드의 실행 결과는?
동적 메모리 할당
5 + 10 + 15 + 20 + 25 + 30 = 105
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *arr = (int *)calloc(4, sizeof(int));
int i, sum = 0;
for (i = 0; i < 4; i++) {
arr[i] = (i + 1) * 5;
}
arr = (int *)realloc(arr, sizeof(int) * 6);
arr[4] = 25;
arr[5] = 30;
for (i = 0; i < 6; i++) {
sum += arr[i];
}
printf("%d", sum);
free(arr);
return 0;
}
핵심 요약
1. 동적 메모리 할당 함수
•
•
•
•
2. 사용 패턴
• 할당:
• NULL 체크:
• 사용 후 해제:
• 해제 후 초기화:
3. 메모리 누수 방지
• 모든 malloc/calloc에 대응하는 free 호출
• 포인터 덮어쓰기 전 기존 메모리 해제
• 함수 반환 전 로컬 동적 메모리 해제
4. 주의사항
• 같은 메모리 두 번 해제 금지
• 해제된 메모리 접근 금지 (댕글링 포인터)
• realloc 실패 시 임시 포인터 사용
5. 활용 예시
• 크기를 모르는 데이터 처리
• 런타임에 결정되는 배열
• 큰 데이터 구조 (연결 리스트, 트리 등)
•
malloc(size): 지정한 크기만큼 할당 (초기화 안 됨)•
calloc(count, size): 할당 + 0으로 초기화•
realloc(ptr, new_size): 크기 재조정•
free(ptr): 메모리 해제2. 사용 패턴
• 할당:
ptr = (type *)malloc(sizeof(type) * n);• NULL 체크:
if (ptr == NULL) { ... }• 사용 후 해제:
free(ptr);• 해제 후 초기화:
ptr = NULL;3. 메모리 누수 방지
• 모든 malloc/calloc에 대응하는 free 호출
• 포인터 덮어쓰기 전 기존 메모리 해제
• 함수 반환 전 로컬 동적 메모리 해제
4. 주의사항
• 같은 메모리 두 번 해제 금지
• 해제된 메모리 접근 금지 (댕글링 포인터)
• realloc 실패 시 임시 포인터 사용
5. 활용 예시
• 크기를 모르는 데이터 처리
• 런타임에 결정되는 배열
• 큰 데이터 구조 (연결 리스트, 트리 등)
PREVIOUS4. 포인터의 심화