0. 우리가 프로그램을 실행할 때,
우리가 코드를 짜고 `실행(run)` 버튼을 눌렀을 때,
그 순간 무슨 일이 일어날까요?
사실 우리가 만든 프로그램은 그 자체로는 그냥 텍스트 파일일 뿐이에요.
코드 자체만으로는 컴퓨터가 이해하거나 실행할 수 없습니다.
그러면 컴퓨터는 어떻게 이걸 “실행”하는 걸까요?
프로그램이 동작하려면?
- 먼저, 코드가 CPU가 이해할 수 있는 언어(기계어)로 변환(컴파일 또는 인터프리팅)되어야 합니다.
- `실행` 버튼을 누르면 → 이 기계어가 RAM(주기억장치)에 로딩 올라갑니다.
- 그제야 CPU가 RAM에 적힌 명령어와 데이터를 읽으며 프로그램이 실제로 동작하게 됩니다.
많은 사람들이 RAM을 단순히 데이터를 “쌓아두는 공간” 정도로 생각합니다.
하지만 실제로는 훨씬 더 정교하고 체계적으로 관리되고 있어요.
운영체제는 프로그램을 RAM에 올릴 때,
그냥 아무 데나 데이터를 올리지 않고 목적에 따라 구역을 나눠서 배치합니다.
코드(Code), 데이터(Data), 힙(Heap), 스택(Stack)

각 영역은 용도와 수명이 다르기 때문에 나누는 거예요.
이 구조를 알면, 우리가 작성한 코드가 메모리에서 어떻게 배치되고,
왜 오버플로우가 발생하며, 어떤 데이터를 언제, 어디에 저장해야 하는지도 쉽게 이해할 수 있어요.
1. 영역별 역할
Code 영역
“이 프로그램이 어떤 일을 하는지 설명해주는 매뉴얼 같은 공간”
프로그램이 실행될 때, 가장 먼저 "내가 무슨 일을 할 건지"를 알려주는 공간이 필요해요.
그게 바로 Code 영역, 또는 텍스트 영역(Text Segment)이라고도 불러요.
특징
- 우리가 작성한 모든 함수들 (`main`, `printf`, 사용자 정의 함수 등)
- 제어문(`if`, `for`, `while` 등)의 처리 로직
- `const`로 선언된 읽기 전용 변수(Read-Only)
- 그리고 이 모든 게 기계어(0과 1)로 변환된 형태로 저장됩니다.
즉, 컴파일이 끝난 후 실행 가능한 명령어들이 담기는 공간입니다.
int add(int a, int b) {
return a + b;
}
이 함수는 컴파일되면 Code 영역에 들어가고,
CPU가 이곳의 명령어를 하나하나 읽으며 동작하게 됩니다.
Data 영역
"언제든 꺼내 쓸 수 있는 재료 창고”
프로그램을 실행하다 보면, 언제 어디서든 접근할 수 있는 값들이 필요합니다.
예를 들면 전역 변수나 `static` 변수처럼, 한 번 저장되면 프로그램이 끝날 때까지 유지되는 값들이죠.
이런 데이터가 저장되는 공간이 바로 Data 영역입니다.
데이터
- 전역 변수: 함수 밖에 선언되어 모든 곳에서 접근 가능한 변수
- 정적 변수 (`static`): 한 번만 초기화되고, 함수가 끝나도 사라지지 않는 변수
- `const`가 아닌 상수들: 읽고 쓰기가 가능한 상수 값들
int globalCount = 0; // 전역 변수 → Data 영역
static int cache = 10; // 정적 변수 → Data 영역
int b; // 초기화되지 않음 → BSS 영역
이 변수들은 프로그램이 시작되면 자동으로 메모리에 올라가고,
프로그램이 끝날 때까지 계속 남아있어요.
특징
- 프로그램 시작 시 자동으로 메모리 할당
- 프로그램 종료 시까지 계속 유지
- 모든 함수, 모든 코드에서 접근 가능
- 값을 저장하면 계속 남아 있으므로 공유 가능
세부구조
- 초기화된 데이터 영역 : `int a = 5;` 처럼 초기값이 있는 변수
- BSS영역: `int b;` 처럼 초기화되지 않은 전역/정적 변수 → 실행 시 0으로 초기화됨
💡BSS 영역은 실제 데이터 대신 공간만 확보 해두었다가,
실행 시 필요한 만큼만 메모리를 채우는 방식으로 효율을 높입니다.
Heap 영역
"필요할 때 빌려쓰는 유동적인 창고”
Heap 영역은 프로그램 실행 중에 얼마나, 언제 필요한지 예측할 수 없는 데이터를 저장하는 공간입니다.
즉, 실행 도중 크기를 결정하거나,
함수 밖에서도 값을 계속 유지하고 싶을 때 사용합니다.
특징
- 크기를 실행 중에 결정해야 할 때
(예: 사용자가 입력한 개수만큼 배열 만들기) - 데이터를 함수 밖에서도 오래 유지하고 싶을 때
- 주로 배열, 객체 등 동적 메모리 할당이 필요한 경우
int* arr = (int*)malloc(N* sizeof(int)); // C
int[] nums = new int[N]; // Java
위의 malloc, new 키워드가 바로 Heap에서 메모리를 빌리는 행위입니다.
- 동적 할당(Dynamic Allocation): 실행 중에 메모리 크기 결정
- 메모리는 수동으로 해제해야 함
- 함수가 끝나도 살아 있으므로 범용성은 높지만 관리가 까다로움
❗ Heap에 할당한 메모리는 반드시 직접 해제해야 합니다.
C: `free()`
C++: `delete`
그렇지 않으면 메모리 누수(memory leak) 발생
Stack 영역
"함수 실행 중 잠깐 사용하는 작업 공간”
Stack은 함수가 실행될 때 잠시 필요로 하는 데이터들을 저장하는 공간입니다.
대표적으로 지역 변수, 매개 변수, 그리고 함수 호출 관련 정보들이 여기에 저장돼요.
구성 요소
- 지역 변수: 함수 안에서 선언된 변수
- 매개 변수: 함수에 전달된 값
- 복귀 주소: 함수가 끝난 뒤 되돌아갈 위치 정보
- 레지스터 상태: 호출 이전의 CPU 상태 저장
💡 이 정보들을 이용해서 함수가 호출됐다가 끝나면, 깔끔하게 되돌아올 수 있는 거예요!
동작방식(LIFO)
Stack은 마지막에 들어간 게 먼저 나오는 구조
void funcA() {
int x = 1;
funcB(); // 호출되면 funcB의 스택 프레임이 위에 쌓임
}
void funcB() {
int y = 2;
}
- `funcA()` 실행 → 스택에 프레임 생성
- `funcB()` 호출 → 위에 새 프레임 추가
- `funcB()` 종료 → 프레임 제거
- 다시 `funcA()`로 복귀
Stack의 장점
- 메모리 자동 정리
→ 함수가 끝나면 관련 데이터도 자동 제거됨 - 따로 `free()` 같은 해제가 필요 없음

2. 실행 시점에 따라 달라지는 메모리의 동작
프로그래밍에서 “언제 메모리를 사용하는가”는 매우 중요한 개념입니다.
메모리는 컴파일 타임(코드 작성 시)과 런타임(프로그램 실행 시)에 따라 다르게 관리됩니다.
컴파일 타임(코드 → 기계어로 바꾸는 단계)
우리가 코드를 작성하고 컴파일하면,
그 코드는 CPU가 이해할 수 있는 기계어로 변환됩니다.
이때 컴파일러는 일부 메모리 구조를 미리 결정합니다:
| 결정되는 것 | 예시 |
| 코드 영역 | 함수, 제어문, 상수 등 |
| 데이터 영역 | 전역 변수, static 변수 등 |
| 스택의 기본 구조 | 지역 변수, 매개변수 공간 등 |
즉, 정적인 정보는 이 시점에 메모리에 할당 준비가 끝납니다.
런타임 (실제로 프로그램이 실행될 때)
프로그램이 실행되면,
사용자의 입력이나 동적으로 결정되는 정보들이 등장합니다.
- 배열의 크기, 객체의 수 등은 이때까지는 알 수 없습니다.
- 그래서 이 시점에 Heap 영역이 동적으로 메모리를 할당합니다.
int* arr = (int*)malloc(n * sizeof(int)); // 런타임에 n의 크기를 보고 메모리 확보
- `new`, `malloc` 등은 모두 런타임 메모리 할당 명령어입니다.
프로그램이 살아 움직이는 동안, 메모리를 필요할 때 빌리고 반납하는 유연한 방식이죠.
3. 스택과 힙
RAM에서는 스택(Stack)과 힙(Heap)이 하나의 물리적 공간을 서로 나눠서 사용합니다.
그리고 이 둘은 반대 방향으로 성장합니다.

- 스택은 위에서 아래로
- 힙은 아래에서 위로
이 둘이 서로를 향해 자라면서, 중간에서 만나면 문제가 발생합니다.
힙과 스택이 충돌하면?
만약:
- 힙이 너무 커져 위로 자라고,
- 동시에 스택도 커져 아래로 내려오면...
결국 중간에서 충돌(Collision)이 일어납니다.
이런 상황을 메모리 충돌(Memory Collision) 또는 오버플로우(Overflow)라고 부릅니다.
스택 오버플로우(Stack Overflow)
| 상황 | 설명 |
| 재귀 함수 무한 호출 | 함수가 자기 자신을 끝없이 호출 → 스택 프레임이 무한히 쌓임 |
| 지역 변수 크기 과도 | 함수 안에서 너무 큰 배열 선언 등 |
| 함수 호출 깊이 과도 | 깊게 중첩된 함수 호출들 |
void f() {
f(); // 끝이 없음 → 스택 터짐!
}
힙 오버플로우(Heap Overflow)
| 상황 | 설명 |
| 너무 많은 메모리 할당 | `malloc(1GB)` 같은 무리한 요청 |
| 메모리 해제를 안 하고 계속 할당 | 누수가 누적되어 힙이 팽창 |
| 잘못된 포인터 연산 | 스택 영역까지 침범할 수 있음 |