데이터가 언제까지 살아있어야 하는지를 생각해봐야 한다.
지역 변수를 스택에 두는 이유
함수 호출에는 규칙적인 순서가 있다. main 에서 A 를 호출하고, A 가 B, B가 C 를 호출하면 C → B → A → main 순서로 끝난다. 끝나는 순서는 반드시 LIFO 구조이다.

이 규칙이 스택 자료구조와 정확히 일치한다. 함수가 호출될 때 그 함수에 들어온 공간(스택 프레임)을 스택 맨 위에 쌓고, 함수가 끝나면 맨 위부터 차례로 제거한다.
이 방법의 장점은 두 가지이다.
- 빠르고 단순하다.
- 공간을 잡는 건 스택 포인터를 내리는 것이고, 반납하는 건 다시 올리는 것뿐이다. 어디가 비었는지 찾을 필요가 없다.
- 자동으로 정리된다.
- 함수가 끝나면 그 함수의 지역 변수는 필요 없으니, 프레임을 걷어내는 순간 같이 반납해도 괜찮다.
지역 변수의 수명이 그 함수가 실행되는 동안 뿐이라, 함수의 호출 순서를 그대로 따라가는 스택이 적합하다.
스택 프레임 반납과 쓰레기 값
함수가 호출되면 실제 계산은 대부분 레지스터에서 일어난다. 필요한 경우 스택 프레임이 쌓이고, 레지스터 만으로 부족한 지역 데이터와 호출 관련 정보가 저장된다. 함수가 끝나면 스택 포인터가 되돌아가면서 그 공간이 반납된다.
함수를 반납하면 프레임의 내용을 지우는 것이 아닌, 스택 포인터를 원래 자리로 되돌리는 것이다. 해당 공간은 사용해도 되는 곳이 되고, 다음에 호출되는 함수가 그 위를 덮어쓴다.
이와 관련하여, 지역변수의 주소를 반환하는 버그들이 일어나기도 한다. 이 공간은 아직 덮어써지지 않았을 뿐, 언제 값이 바뀔지 모르는 상태이다.
힙은 왜 필요한가
수명이 함수 호출 순서와 안 맞는 데이터들을 따로 관리할 필요가 있다.
int *make_array(int n) {
int arr[100]; // 스택: 함수 끝나면 사라짐
return arr; // ❌ 이미 사라질 공간의 주소를 돌려줌
}
1. 수명이 함수 호출 순서와 안 맞는데 데이터
make_array 가 끝나는 순간 그 스택 프레임은 걷어지므로, 돌려받은 주소는 이미 다른 함수가 덮어쓸 수 있는 공간을 가리킨다. 함수가 끝난 뒤에도 살아 있어야 하는 데이터는 스택에 두면 안된다.
2. 크기를 실행 중에야 알 수 있는 경우 (malloc)
사용자가 입력한 개수만큼 배열이 필요하다면, 컴파일 시점에 크기를 정해두기 어렵다.
이런 데이터들은 힙에 둔다. 힙은 호출 순서와 상관없이 원할 때 malloc 으로 잡고, 다 쓰면 free 로 직접 반납하는 공간이다. 어디가 비어 있는지 관리해야 하니 스택보다 느리고, 프로그래머가 구현 시 free 를 잊으면 메모리 누수가 발생한다.
int *make_array(int n) {
int *arr = malloc(n * sizeof(int)); // 힙: 함수 끝나도 살아 있음
return arr; // ✅ 호출한 쪽이 나중에 free
}
정리
스택
수명이 함수 호출과 같이 시작하고 끝나는 데이터 → 호출 순서 (LIFO) 를 그대로 따르니 빠르고 자동 정리됨.
힙
합수가 끝나도 살아야 하거나 크기를 실행 중에 정하는 데이터 → 유연하지만 직접 관리해야 함.
번외
C 는 힙이라는 이름으로 부르지 않고, 구현 방식을 정해두지 않는다. C 에 있는 것은 malloc/free 로 원할 때 잡고 직접 반납하는 메모리이다. 할당 저장 기간이라는 개념은 있지만, 메모리가 주소 공간의 어떤 영역에 있는지 관리되는지는 정해져 있지 않다.
실제로는 리눅스 같은 시스템에서는 malloc 이 힙 영역을 관리해서 메모리를 나눠준다. 때문에 C 의 malloc = 힙 이라고 통용된다.