힙(Heap) 오버플로란 무엇인가?
힙(heap)은 프로그램 실행 중 동적으로 할당되는 변수를 저장하는 프로세스 메모리 영역입니다. C 언어에서는 malloc(), calloc(), realloc() 같은 표준 라이브러리 함수를 통해 힙 영역에 메모리를 할당합니다.
힙 오버플로(heap overflow)는 주로 다음과 같은 상황에서 발생합니다.
A) 비정상적으로 큰 동적 메모리를 한 번에 할당하는 경우
int main() {
float *ptr = (float *)malloc(sizeof(float) * 1000000);
}위 코드처럼 실제 필요한 양보다 지나치게 큰 메모리를 요청하면, 시스템이 충분한 힙 공간을 확보하지 못해 오버플로가 발생할 수 있습니다.
B) 메모리를 계속 할당만 하고 해제하지 않는 경우
int main() {
for (int i = 0; i < 100000000000; i++) {
int *p = (int *)malloc(sizeof(int));
}
}반복문 안에서 계속 메모리를 할당하고 free()로 해제하지 않으면 사용 가능한 힙 공간이 점점 고갈되다가 결국 오버플로가 발생합니다. 이처럼 사용 후 해제되지 않은 메모리가 쌓이는 현상을 메모리 누수(memory leak)라고 부릅니다.
스택(Stack) 오버플로란 무엇인가?
스택(stack)은 LIFO(Last In First Out, 후입선출) 구조로 동작하는 메모리 영역으로, 함수 내부에서 사용되는 지역 변수와 매개변수, 함수의 반환 주소 등을 저장합니다.
컴퓨터 메모리에서 스택의 크기는 제한적이기 때문에, 프로그램이 이 한계를 초과하는 메모리를 사용하면 스택 오버플로(stack overflow)가 발생합니다.
C) 함수가 자기 자신을 무한히 재귀 호출하는 경우
void calculate(int a) {
if (a == 0)
return;
a = 6;
calculate(a);
}
int main() {
int a = 5;
calculate(a);
}재귀 호출이 종료 조건 없이 무한히 반복되면, 함수가 호출될 때마다 새로운 지역 변수와 반환 주소가 스택에 계속 쌓입니다. 스택에는 이렇게 많은 데이터를 저장할 공간이 없으므로 결국 오버플로가 발생합니다.
D) 크기가 큰 지역 변수나 대형 배열을 선언하는 경우
int main() {
int A[20000][20000];
}차원이 큰 배열이나 행렬을 지역 변수로 선언하면 스택에 할당해야 할 메모리가 한계를 초과하여 스택 오버플로가 일어날 수 있습니다. 이런 경우에는 배열을 전역 변수로 선언하거나, malloc()을 이용해 힙에 동적 할당하는 것이 안전합니다.
정리: 힙 오버플로 vs 스택 오버플로
| 구분 | 힙 오버플로 | 스택 오버플로 |
|---|---|---|
| 저장 대상 | 동적 할당 변수 | 지역 변수, 매개변수, 반환 주소 |
| 주요 원인 | 과도한 동적 할당, 메모리 미해제(누수) | 무한 재귀 호출, 대형 지역 배열 선언 |
| 예방 방법 | 사용 후 free()로 반드시 해제, 적절한 크기 할당 | 명확한 종료 조건 설정, 배열 크기 축소 또는 힙 활용 |
두 오버플로 모두 프로그램 비정상 종료나 보안 취약점으로 이어질 수 있으므로, 메모리 할당 크기를 신중하게 설계하고 재귀 함수에는 항상 종료 조건을 두는 습관이 중요합니다.