CS
Stack Overflow와 Memory Leak의 차이
C++에서 지역 변수는 stack에 잡히고, new로 만든 객체는 heap에 잡힌다는 설명을 한 번쯤 들어봤을 것입니다.
그런데 실무에서 memory 문제를 볼 때는 이 문장만으로 부족합니다.
stack overflow, memory leak, dangling pointer, large allocation, recursion, global object 초기화 문제는 모두 Process memory layout을 이해해야 제대로 해석할 수 있습니다.
Process memory layout은 프로그램 코드, 전역 데이터, 동적 할당 객체, 함수 호출 정보를 운영체제가 한 Process의 Virtual address space 안에서 어떻게 구분해 관리하는지 보여주는 지도입니다.
이번 글에서는 Text, Data/BSS, Heap, Stack 영역이 각각 무엇을 담는지, stack과 heap을 단순히 빠르고 느린 공간으로만 보면 왜 위험한지, C++ 코드와 연결해서 정리해보겠습니다.

1. Process memory layout은 무엇인가?
Process는 실행 중인 프로그램입니다.
운영체제는 각 Process에 독립된 Virtual address space를 제공하고, 프로그램은 이 주소 공간 안에서 code, data, stack, heap을 사용하는 것처럼 동작합니다.
실제 Physical memory의 어디에 올라가는지는 운영체제와 MMU가 관리합니다.
개발자가 보는 주소는 대부분 Virtual address입니다.
그래서 서로 다른 Process에서 같은 주소처럼 보이는 값이 있어도 실제 Physical memory가 같다는 뜻은 아닙니다.
| 영역 | 주로 담는 것 | 실무에서 연결되는 문제 |
| Text | 컴파일된 machine code, read-only instruction | code sharing, 실행 파일 크기, instruction cache |
| Data / BSS | global object, static object, 초기화된 데이터와 0으로 초기화되는 데이터 | global state, initialization order, process 시작 메모리 |
| Heap | dynamic allocation으로 확보한 객체 | memory leak, fragmentation, allocation latency |
| Stack | function call frame, local variable, return address 관련 정보 | stack overflow, recursion depth, large local object |
이 구분은 단순 암기보다 문제를 해석하는 기준으로 중요합니다.
예를 들어 큰 배열을 지역 변수로 선언해 crash가 났다면 heap 문제가 아니라 stack size 문제일 수 있습니다.
반대로 요청을 처리할 때마다 객체를 heap에 만들고 해제하지 않는다면 stack이 아니라 heap 사용량이 계속 증가합니다.
2. 왜 중요한가?
메모리 영역을 구분하지 못하면 장애 원인을 엉뚱한 곳에서 찾기 쉽습니다.
서버가 OOM으로 죽었을 때 모든 문제가 memory leak은 아닙니다.
large allocation이 순간적으로 몰렸을 수도 있고, allocator fragmentation이 심해졌을 수도 있고, thread 수가 늘어 stack 예약 공간이 커졌을 수도 있습니다.
- 재귀 함수가 깊어져 stack overflow가 발생할 수 있다.
- heap 객체를 해제하지 않아 process RSS가 계속 증가할 수 있다.
- thread를 많이 만들면 각 thread stack 때문에 memory pressure가 커질 수 있다.
- global object가 많으면 process 시작 비용과 초기화 순서 문제가 생길 수 있다.
- C++ 객체의 lifetime과 storage duration을 혼동하면 dangling pointer를 만들 수 있다.
기술 면접에서도 이 주제는 자주 나옵니다.
좋은 답변은 “stack은 빠르고 heap은 느리다”에서 끝나지 않습니다.
어떤 객체가 어떤 lifetime을 가지는지, allocation과 deallocation 책임이 어디에 있는지, 운영체제가 Virtual memory를 어떻게 보여주는지까지 연결해야 합니다.
3. 그림으로 이해하기
단순화한 Process memory layout은 다음처럼 볼 수 있습니다.
중요한 점은 이 그림이 물리 메모리 배치도가 아니라 Process가 바라보는 Virtual address space의 개념도라는 것입니다.
또한 현대 운영체제에서는 ASLR 때문에 실행할 때마다 주소가 달라질 수 있습니다.
따라서 그림의 정확한 주소값을 외우기보다, 각 영역이 어떤 lifetime과 접근 패턴을 가지는지 이해하는 편이 실무적으로 더 중요합니다.
4. Stack은 함수 호출의 작업 공간이다
Stack은 함수가 호출될 때마다 call frame을 쌓는 공간입니다.
call frame에는 local variable, return address, 저장해야 하는 register 값, 함수 호출에 필요한 부가 정보가 들어갈 수 있습니다.
함수가 끝나면 해당 frame은 한 번에 사라집니다.
이 특성 때문에 stack allocation은 보통 빠르고 관리가 단순합니다.
하지만 stack은 크기가 제한되어 있습니다.
큰 배열을 지역 변수로 만들거나, recursion depth가 깊어지거나, thread를 과도하게 만들면 stack overflow가 발생할 수 있습니다.
void risky()
{
int values[10'000'000]; // 너무 큰 local array
values[0] = 1;
}
예상 결과
환경에 따라 stack overflow 또는 segmentation fault가 발생할 수 있음
같은 크기의 데이터라도 heap allocation으로 옮기면 증상이 달라질 수 있음
이 예제의 핵심은 “배열이 크다”가 아니라 “큰 객체를 제한된 stack에 올렸다”는 점입니다.
실무에서는 큰 buffer나 큰 collection을 지역 변수로 두기 전에 크기와 호출 빈도를 함께 봐야 합니다.
5. Heap은 동적 lifetime을 위한 공간이다
Heap은 실행 중 필요한 크기와 lifetime이 정해지는 객체를 담기 위해 사용됩니다.
C++에서는 new/delete, std::make_unique, std::make_shared, standard container 내부 allocation이 heap과 연결됩니다.
Heap allocation은 stack처럼 함수가 끝난다고 자동으로 사라지지 않습니다.
객체의 lifetime을 명확히 관리해야 하고, 관리가 실패하면 memory leak이나 use-after-free 같은 문제가 생깁니다.
| 구분 | Stack | Heap |
| 주요 용도 | 함수 호출 중 필요한 local object | 동적 크기 또는 함수 밖까지 살아야 하는 object |
| 해제 시점 | scope 종료와 함께 자동 정리 | 소유권 규칙에 따라 명시적 또는 RAII로 정리 |
| 주요 위험 | stack overflow, dangling reference | memory leak, fragmentation, use-after-free |
| 실무 판단 기준 | 작고 lifetime이 짧은가? | 크기가 크거나 lifetime이 동적인가? |
Heap이 무조건 나쁘고 stack이 무조건 좋은 것은 아닙니다.
문제는 객체의 lifetime과 크기에 맞지 않는 영역을 선택하는 것입니다.
큰 데이터를 stack에 억지로 올리면 stack overflow가 생길 수 있고, 모든 것을 heap에 흩뿌리면 allocation 비용과 cache locality 문제가 커질 수 있습니다.
6. C++ 코드로 연결해보기
다음 코드는 global object, static object, stack object, heap object가 어떻게 다른 storage duration을 가지는지 보여줍니다.
주소값 자체는 실행 환경마다 달라지므로 숫자를 외우면 안 됩니다.
#include <iostream>
#include <memory>
int globalValue = 10;
int main()
{
static int staticValue = 20;
int stackValue = 30;
auto heapValue = std::make_unique<int>(40);
std::cout << "globalValue address: " << &globalValue << '\n';
std::cout << "staticValue address: " << &staticValue << '\n';
std::cout << "stackValue address: " << &stackValue << '\n';
std::cout << "unique_ptr object address: " << &heapValue << '\n';
std::cout << "managed int address: " << heapValue.get() << '\n';
}
실행 결과
globalValue address: 0x...
staticValue address: 0x...
stackValue address: 0x...
unique_ptr object address: 0x...
managed int address: 0x...
여기서 자주 놓치는 부분은 std::unique_ptr 객체 자체와, std::unique_ptr이 관리하는 int 객체가 다르다는 점입니다.
위 코드에서 heapValue라는 local variable은 stack frame 안에 있습니다.
하지만 heapValue가 가리키는 int는 heap에 동적으로 할당됩니다.
즉, smart pointer를 쓴다고 해서 모든 것이 heap에 있는 것도 아니고, pointer variable이 stack에 있다고 해서 가리키는 객체도 stack에 있다는 뜻은 아닙니다.
7. 자주 하는 오해
Process memory layout을 볼 때는 다음 오해를 피해야 합니다.
- stack은 항상 heap보다 좋다.
작고 짧게 쓰는 객체에는 stack이 자연스럽지만, 큰 객체나 동적 lifetime이 필요한 객체에는 heap이 더 적합할 수 있습니다. - pointer는 heap에 있다.
pointer variable은 stack, global, heap 어디에든 있을 수 있습니다. pointer가 가리키는 대상의 위치와 pointer variable의 위치는 별개입니다. - std::vector는 stack container다.
std::vector 객체 자체는 local variable이면 stack에 있을 수 있지만, element 저장 공간은 보통 heap에 allocation됩니다. - Text/Data/Heap/Stack 순서는 모든 환경에서 같다.
교육용 그림은 단순화된 모델입니다. 실제 주소 배치는 운영체제, ABI, ASLR, loader, memory mapping에 따라 달라집니다. - memory leak은 heap에서만 생각하면 된다.
일반적으로 leak은 heap allocation과 연결되지만, file descriptor, socket, handle 같은 resource leak도 process 안정성에 큰 영향을 줍니다.
핵심은 영역 이름을 외우는 것이 아니라, 객체가 언제 만들어지고 언제 사라지는지 추적하는 것입니다.
8. 실무에서는 어떻게 볼까?
실무에서 memory 문제를 볼 때는 먼저 증상을 영역별로 나누면 좋습니다.
recursion이 깊어지거나 특정 요청에서만 즉시 crash가 난다면 stack 사용량을 의심할 수 있습니다.
트래픽이 누적될수록 process memory가 계속 증가한다면 heap allocation, cache, container growth, resource ownership을 확인해야 합니다.
thread 수가 많아진 뒤 memory pressure가 커졌다면 각 thread stack 예약 공간도 봐야 합니다.
| 증상 | 먼저 볼 영역 | 확인할 질문 |
| 깊은 recursion에서 crash | Stack | call depth와 local object 크기가 큰가? |
| 시간이 지날수록 RSS 증가 | Heap | 해제되지 않는 allocation이나 cache growth가 있는가? |
| thread 증가 후 memory pressure | Stack / Heap | thread stack size와 per-thread buffer가 큰가? |
| 시작 직후 memory가 큼 | Data / BSS | global/static object가 과도하게 큰가? |
| 객체가 사라진 뒤 접근 | Lifetime | dangling pointer나 reference가 남아 있는가? |
C++에서는 RAII와 smart pointer를 쓰는 이유도 여기에 있습니다.
Heap allocation 자체를 없애는 것이 목표가 아니라, ownership과 lifetime을 코드 구조 안에 명확히 표현하는 것이 목표입니다.
성능 관점에서는 allocation 횟수, object locality, container growth, allocator 동작까지 함께 봐야 합니다.
9. 면접 질문 예시
이 주제는 운영체제 기본기와 C++ lifetime 감각을 함께 묻기 좋습니다.
- Stack과 Heap의 차이를 설명해보세요.
Stack은 function call frame과 local object 중심의 자동 관리 공간이고, Heap은 dynamic allocation으로 얻는 공간이라고 설명한 뒤 lifetime, size limit, allocation cost를 함께 말하면 좋습니다. - std::unique_ptr로 만든 객체는 stack에 있나요, heap에 있나요?
std::unique_ptr local variable 자체는 stack에 있을 수 있지만, std::make_unique로 생성한 관리 대상 객체는 heap에 있다고 구분해야 합니다. - Stack overflow와 memory leak은 어떻게 다르게 접근해야 하나요?
Stack overflow는 call depth와 local object 크기, thread stack size를 보고, memory leak은 heap allocation ownership과 release path를 확인한다고 답할 수 있습니다.
면접 답변에서는 영역 이름보다 lifetime과 ownership을 정확히 구분하는 것이 중요합니다.
10. 정리
Process memory layout은 Text, Data/BSS, Heap, Stack 같은 영역으로 Process의 Virtual address space를 이해하는 모델입니다.
Stack은 function call frame과 local object를 관리하며, 크기 제한 때문에 recursion이나 큰 local object에 취약할 수 있습니다.
Heap은 동적 lifetime을 위한 공간이며, ownership 관리가 실패하면 memory leak이나 use-after-free가 발생할 수 있습니다.
Pointer variable의 위치와 pointer가 가리키는 객체의 위치는 별개입니다.
실무에서는 stack vs heap을 속도 비교로만 보지 말고, object lifetime, allocation pattern, memory pressure를 함께 봐야 합니다.
'CS' 카테고리의 다른 글
| Page table과 TLB로 이해하는 OS memory 관리 (0) | 2026.07.10 |
|---|---|
| System call과 Context switch 차이 (1) | 2026.07.07 |
| TCP 3-way handshake 쉽게 이해하기 (0) | 2026.06.30 |
| DB 인덱스가 있는데도 쿼리가 느린 이유 (0) | 2026.06.26 |
| Race condition이란? 동시성 버그와 임계 구역 이해하기 (0) | 2026.06.23 |
'CS'의 다른글
- 현재글Stack Overflow와 Memory Leak의 차이