이 Step에서 다루는 것
- 스택이 “함수들이 같이 쓰는 임시 공간”이라는 말이 정확히 무슨 뜻인지
- 함수 호출/리턴이 일어날 때 스택이 어떻게 쌓이고(호출) 어떻게 정리되는지(리턴)
- 스택 오버플로우가 왜/언제 터지는지, 그리고 디버깅에서 어떻게 보이는지
학습 목표
- 스택을 한 문장으로 정의할 수 있다.
- LIFO와 “주소 방향(높은→낮은)”을 헷갈리지 않는다.
- 스택 오버플로우의 대표 원인 2가지를 말할 수 있다. (무한 재귀 / 큰 지역 변수)
스택이란?
- 함수 호출 과정에서 “잠깐 필요한 정보”를 쌓아두는 메모리 영역입니다.
- 스택에 들어가는 대표 정보:
- 반환 주소(Return Address): 함수가 끝나면 “어디로 돌아갈지”
- 매개변수(Parameter): 호출 시 넘긴 값(대부분 “복사”)
- 지역 변수(Local): 함수 내부에서 만든 임시 데이터
- LIFO(후입선출): 가장 나중에 들어온(가장 마지막에 호출된) 함수의 정보가 가장 먼저 정리됩니다.
- 주소 방향(개념): 보통 높은 주소 → 낮은 주소 방향으로 “쌓인다”라고 설명합니다.
- 크기: 일반적으로 프로그램 시작 시 일정 크기를 잡아두고(수 MB 단위가 흔함), 호출/리턴에 따라 늘었다 줄었다 합니다.
스택을 그림으로 잡으면 이런 느낌입니다.
높은 주소
┌──────────────────────────────┐
│ main()의 스택 프레임 │
├──────────────────────────────┤
│ A()의 스택 프레임 │ ← A() 호출
├──────────────────────────────┤
│ B()의 스택 프레임 │ ← B() 호출 (가장 최근)
└──────────────────────────────┘
낮은 주소
리턴 순서: B() → A() → main()
자주 하는 오해 2가지
- “리턴하면 메모리가 0으로 초기화되나요?”
보통은 아닙니다. “사용하지 않는 영역”으로만 바뀌고, 값은 남아 있을 수 있습니다. 그래서 초기화 없이 읽으면 “쓰레기 값”이 나올 수 있습니다.
- “스택은 영구 저장소인가요?”
절대 아닙니다. 함수가 끝나면 해당 영역은 더 이상 내 것이 아니고, 다음 호출에서 덮어써질 수 있습니다.
비유: 슈퍼마켓 계산대
스택을 “공용 메모장 1권”으로 비유하면 이해가 빨라집니다.
- 메모장(스택)은 하나고, 여러 사람이(함수들이) 순서대로 씁니다.
- 어떤 함수가 자기 일을 하려면, 먼저 “여기부터 여기까지 내 자리”라고 경계선을 긋고 사용합니다.
- 다른 함수가 호출되면(새 손님), 앞사람이 쓰던 자리 아래에 새로 경계선을 긋고 이어서 씁니다.
- 일이 끝나면(리턴), 지우고 정리하는 게 아니라 “그 사람의 자리”를 반납해서 이전 사람이 계속 쓰게 됩니다.
main()이 쓰다가
┌───────────────┐
│ main() │ ← 여기까지 사용
└───────────────┘
main()이 A()를 호출하면
┌───────────────┐
│ main() │ (그대로)
├───────────────┤
│ A() │ ← A()가 추가로 사용
└───────────────┘
A()가 B()를 호출하면
┌───────────────┐
│ main() │
├───────────────┤
│ A() │
├───────────────┤
│ B() │ ← 가장 최근 호출
└───────────────┘
B()가 끝나면 B() 자리만 반납 → A()로 복귀
- 핵심 결론: 스택은 “한 번 쓰고 버리는 임시 공간”에 가깝습니다.
(강의 비유대로라면 “인터스텔라 웨이브”처럼 언제든 덮어쓰일 수 있는 불안정한 공간)
스택 오버플로우 (Stack Overflow)
- 스택에 쌓이는 데이터(스택 프레임)가 너무 깊거나(너무 많이 호출), 너무 크면(지역 변수가 너무 큼) 스택 공간을 넘어서면서 터집니다.
- 대표 원인:
- 무한 재귀 / 종료 조건 누락
- 너무 큰 지역 변수(특히 큰 배열) 를 함수 내부에 생성
무한 재귀로 터지는 예시
void RecursiveFunction() {
RecursiveFunction();
}
int main() {
RecursiveFunction();
}
- 무한 재귀 호출로 스택 프레임이 계속 쌓이고, 한계를 넘는 순간 프로그램이 강제 종료(크래시) 됩니다.
“종료 조건이 있는 재귀”의 최소 형태
void RecursiveFunction(int depth)
{
if (depth <= 0)
return;
RecursiveFunction(depth - 1);
}
디버깅 힌트
- 스택 오버플로우가 의심될 때는 디버거의 Call Stack(호출 스택) 을 보면,
같은 함수가 끝없이 반복되는 형태로 쌓여 있는 경우가 많습니다.