"첫 프로그램을 짤 때부터 스택을 써왔어. 눈에 안 보였을 뿐이지. 함수를 호출할 때마다 스택에 push하고, return할 때마다 거기서 pop해 왔거든."
마지막에 들어온 게 먼저 나간다
스택의 규칙은 하나야. 마지막에 넣은 걸 먼저 꺼낸다. push는 맨 위에 얹고, pop은 맨 위에서 걷어내고, peek은 걷어내지 않고 맨 위만 들여다봐. 셋 다 O(1)이야. "맨 위"에서는 아무것도 밀 일이 없으니까. 접시 더미를 떠올리면 딱 맞아. 올릴 때도 뺄 때도 맨 위고, 맨 아래 접시를 쓰려면 위에 쌓인 걸 전부 치워야 하잖아.
콜 스택: 이미 기대고 있던 스택
여기서 모든 게 맞물려. 콜 스택, 그러니까 함수를 실제로 돌려주는 그 메커니즘이 말 그대로 스택이야. 함수 A가 B를 부르면 B의 프레임(지역 변수와 돌아갈 주소)이 A의 프레임 위에 push돼. B가 반환하면 그 프레임이 pop되고 제어는 A가 멈췄던 바로 그 자리로 떨어지지. 호출이 중첩되면 프레임이 쌓이고, 반환은 역순으로 풀려. 그래서 재귀 깊이가 곧 스택 깊이야. 복잡도 트랙에서 본 그 공간 비용이 이거고. 무한 재귀가 RecursionError를 던지는 이유도 여기 있어. 말 그대로 콜 스택을 넘쳐버린 거니까. 자료구조 하나를 배운 게 아니라, 그동안 모든 코드를 굴려온 물건의 정체를 배운 거야.
스택이 빛나는 곳: 매칭과 중첩
중첩이나 매칭이 걸린 문제는 웬만하면 스택 문제야. 괄호 균형 맞추기, 유효한 HTML/XML 태그, 코드의 괄호 짝 맞추기, 산술식 계산, 실행 취소 버튼(행동마다 push하고 취소할 때 가장 최근 걸 pop). 패턴은 하나로 똑같아. 열릴 때 push, 닫힐 때 pop, 그리고 pop한 게 지금 닫는 것과 짝이 맞는지 확인. pop해야 하는데 스택이 비어 있거나, 다 끝났는데 스택에 뭔가 남아 있으면 중첩이 깨진 거야. 이 패턴을 깊이 우선 탐색의 엔진으로 한 번 더 만나게 돼.
피파의 고백
push하고 pop하며 배우던 바로 그 LIFO 구조라고. 그 순간 내 무한 재귀 크래시가 물리적으로 이해가 됐어. 프레임을 끝없이 push해서 진짜 스택을 넘쳐버린 거였으니까. 내가 공부하던 추상화가 알고 보니 내가 내내 딛고 서 있던 바닥이었던 거야.