메모리 구조 (프로세스 메모리 레이아웃)
프로그램을 실행하면 운영체제가 그 프로세스만 쓰는 메모리 공간을 하나 내어 줍니다. 이것을 프로세스 주소 공간이라고 하고, 안쪽은 하는 일에 따라 Text(Code), Data, BSS, Heap, Stack 영역으로 나뉩니다.
비유: 아파트 층별 구조
프로세스가 사용하는 메모리를 아파트라고 생각해보세요. 각 층마다 역할이 다릅니다.
높은 주소
┌─────────────────────────┐
│ Stack │ ← 5층: 함수 호출 기록, 지역변수
│ (아래로 성장 ↓) │ (자동 관리 - 함수 끝나면 사라짐)
├─────────────────────────┤
│ ↕ │ ← 빈 공간 (Stack ↔ Heap이 서로 마주보며 성장)
├─────────────────────────┤
│ Heap │ ← 4층: 동적 할당 공간 **누수 발생 지점!**
│ (위로 성장 ↑) │ (수동 관리 - 개발자가 직접 해제해야 함)
├─────────────────────────┤
│ BSS │ ← 3층: 초기화 안 된 전역변수
├─────────────────────────┤
│ Data │ ← 2층: 초기화된 전역변수
├─────────────────────────┤
│ Text │ ← 1층: 실행 코드 자체
└─────────────────────────┘
낮은 주소이 문서의 누수는 4층(Heap)에서 발생! 짐을 계속 쌓기만 하고 치우지 않아서 아파트가 꽉 찬 상황
영역별로 자세히 보기
| 영역 | 무엇이 들어가나 | 크기가 정해지는 때 | 특징 |
|---|---|---|---|
| Text (Code) | 실행할 기계어 명령어 | 컴파일할 때 | 읽기 전용. 같은 프로그램을 여러 개 띄우면 이 영역을 함께 쓸 수 있음 |
| Data | 초깃값이 있는 전역 변수, static 변수 | 컴파일할 때 | 프로그램이 끝날 때까지 남아 있음 |
| BSS | 초깃값이 없는 전역 변수, static 변수 | 컴파일할 때 | 실행할 때 0으로 채워짐 |
| Heap | malloc, new, 파이썬 객체처럼 실행 중에 만드는 데이터 | 실행할 때 | 개발자(또는 GC)가 직접 정리해야 함. 높은 주소 쪽으로 자람 |
| Stack | 함수 매개변수, 지역 변수, 돌아갈 주소 | 실행할 때 | 함수가 끝나면 자동으로 사라짐. 낮은 주소 쪽으로 자람 |
- Heap과 Stack은 빈 공간을 사이에 두고 서로 마주 보며 자랍니다. 함수를 너무 깊게 부르면(예: 끝나지 않는 재귀) Stack이 정해진 크기를 넘어서 스택 오버플로가 나고, Heap을 쓰고 치우지 않으면 메모리 누수가 생깁니다.
왜 이렇게 영역을 나눌까
핵심은 함께 쓸 수 있는 것은 함께 써서 메모리를 아끼고, 성격이 다른 데이터는 관리 방식을 다르게 하기 위해서입니다.
- Code를 따로 두는 이유: 같은 프로그램이면 명령어 내용이 모두 똑같습니다. 크롬 창을 여러 개 띄워도 코드는 한 벌만 메모리에 두고 함께 읽으면 됩니다. 그래서 읽기 전용으로 따로 관리합니다.
- Stack과 Data를 나누는 이유: 함수와 지역 변수는 "나중에 부른 함수가 먼저 끝나는" 순서(LIFO)로 생기고 사라지므로 스택 구조가 딱 맞습니다. 반면 여러 함수가 같이 쓰는 전역 변수는 함수가 끝나도 남아 있어야 하므로, 스택이 아닌 Data 영역에 한 번만 두고 모두가 참조하게 하면 메모리를 아낄 수 있습니다.
- Heap을 따로 두는 이유: 크기를 실행해 봐야 아는 데이터(예: 사용자가 올린 파일 개수)는 미리 자리를 잡을 수 없습니다. 필요할 때 빌리고 다 쓰면 돌려주는 공간이 따로 필요합니다.
스레드는 어떤 영역을 함께 쓸까
한 프로세스 안의 스레드들은 Stack만 각자 따로 갖고, Text, Data, BSS, Heap은 함께 씁니다. 각 스레드가 서로 다른 함수를 동시에 실행하려면 함수 호출 기록(Stack)은 따로 있어야 하기 때문입니다. 자세한 내용은 프로세스와 스레드를 참고하세요.
text
프로세스 하나
┌─────────────────────────────────────┐
│ Text │ Data │ BSS │ Heap ← 모두 공유 │
├─────────────────────────────────────┤
│ Stack(스레드1) │ Stack(스레드2) │ ... │ ← 스레드마다 따로
└─────────────────────────────────────┘정리
| 영역 | 역할 | 관리 · 크기 |
|---|---|---|
| Stack | 함수 호출 기록, 지역변수 (아래로 성장) | 자동 (함수 끝나면 사라짐) |
| Heap | 동적 할당 공간 (위로 성장), 누수 발생 지점 | 수동 (개발자가 직접 해제) |
| BSS | 초기화 안 된 전역변수 (실행 시 0으로 채움) | 컴파일 때 고정 |
| Data | 초기화된 전역변수, static 변수 | 컴파일 때 고정 |
| Text | 실행 코드 자체, 읽기 전용이라 같은 프로그램끼리 공유 가능 | 컴파일 때 고정 |
| 스레드 | Stack만 따로, 나머지 영역은 같은 프로세스의 스레드끼리 공유 |
출처: Codyssey-B1/B4-2
