[Daily morning study] 프로세스 메모리 구조 — 스택, 힙, 텍스트, 데이터 세그먼트
#daily morning study
프로세스 메모리 구조란
운영체제는 프로세스를 실행할 때 각 프로세스에게 독립된 가상 주소 공간(Virtual Address Space)을 할당한다. 이 공간은 목적별로 구분된 여러 세그먼트로 나뉜다.
높은 주소 (0xFFFFFFFF)
┌─────────────────┐
│ 커널 영역 │ ← 사용자 모드에서 접근 불가
├─────────────────┤
│ 스택 │ ↓ 아래로 성장
│ │
│ (사용 안 함) │
│ │
│ 힙 │ ↑ 위로 성장
├─────────────────┤
│ BSS 세그먼트 │ (초기화되지 않은 전역/정적 변수)
├─────────────────┤
│ 데이터 세그먼트 │ (초기화된 전역/정적 변수)
├─────────────────┤
│ 텍스트 세그먼트 │ (실행 코드)
└─────────────────┘
낮은 주소 (0x00000000)
각 세그먼트 상세
텍스트 세그먼트 (Text / Code Segment)
- 컴파일된 실행 코드(기계어 명령어)가 저장된다.
- 읽기 전용(Read-Only)으로 설정되어 있어 코드 수정 시 segfault가 발생한다.
- 같은 프로그램을 여러 프로세스가 실행할 때 텍스트 세그먼트를 공유할 수 있다.
데이터 세그먼트 (Data Segment)
- 초기값이 있는 전역 변수, 정적(static) 변수가 저장된다.
- 프로그램 시작 시 초기값이 세팅된다.
int count = 10; // 데이터 세그먼트
static double pi = 3.14; // 데이터 세그먼트
BSS 세그먼트 (Block Started by Symbol)
- 초기값이 없거나 0으로 초기화된 전역/정적 변수가 저장된다.
- 실행 파일에는 크기 정보만 기록되고, OS가 로드 시 0으로 채워 준다 → 실행 파일 크기 절약.
int buffer[1024]; // BSS 세그먼트 (초기값 없음)
static int flag; // BSS 세그먼트
힙 (Heap)
- 프로그래머가 동적으로 할당/해제하는 메모리 영역이다.
- 낮은 주소 → 높은 주소 방향으로 성장한다.
- C에서는
malloc/free, C++에서는new/delete, Java/Python은 GC가 관리한다. - 힙 영역이 스택 영역을 침범하면 힙 오버플로우(Heap Overflow) 발생.
int *arr = (int *)malloc(sizeof(int) * 100); // 힙에 400바이트 할당
free(arr); // 해제하지 않으면 메모리 누수(Memory Leak)
스택 (Stack)
- 함수 호출 시마다 스택 프레임(Stack Frame)이 쌓이고, 함수가 반환되면 제거된다.
- 높은 주소 → 낮은 주소 방향으로 성장한다.
- 지역 변수, 함수 인자, 반환 주소(Return Address)가 저장된다.
- 크기가 고정되어 있어 재귀가 너무 깊거나 대형 배열을 지역 변수로 선언하면 스택 오버플로우(Stack Overflow) 발생.
void foo(int x) {
int local = x * 2; // 스택에 저장
// 함수 반환 시 local, x 모두 사라짐
}
스택 vs 힙 비교
| 항목 | 스택 | 힙 |
|---|---|---|
| 할당 주체 | 컴파일러 자동 | 프로그래머 또는 GC |
| 성장 방향 | 높은 → 낮은 주소 | 낮은 → 높은 주소 |
| 속도 | 빠름 (SP 레지스터만 조정) | 상대적으로 느림 (단편화 처리) |
| 크기 | 제한적 (보통 수 MB) | 시스템 메모리 한도까지 |
| 생명주기 | 함수 스코프 | 명시적 해제 전까지 |
| 오버플로우 원인 | 무한 재귀, 대형 지역 배열 | 메모리 누수 누적 |
스택 프레임 구조
함수를 호출할 때마다 스택에 아래 정보가 순서대로 쌓인다.
[높은 주소]
┌──────────────────────┐
│ 이전 함수의 스택 │
├──────────────────────┤ ← 이전 BP(Base Pointer)
│ 리턴 주소 │ ← 함수 반환 후 돌아갈 명령어 주소
│ 함수 인자 │
│ 저장된 레지스터 값 │
│ 지역 변수 │
└──────────────────────┘ ← 현재 SP(Stack Pointer)
[낮은 주소]
- 스택 오버플로우 공격(Buffer Overflow)의 핵심이 바로 이 리턴 주소를 덮어쓰는 것이다.
예시로 보는 변수 저장 위치
#include <stdio.h>
#include <stdlib.h>
int global_init = 42; // 데이터 세그먼트
int global_uninit; // BSS 세그먼트
void foo() {
int local = 10; // 스택
int *heap = malloc(4); // heap 포인터는 스택, 가리키는 공간은 힙
*heap = 99;
free(heap);
}
int main() {
static int s = 5; // 데이터 세그먼트 (static)
foo();
return 0;
}
ASLR (Address Space Layout Randomization)
현대 OS는 ASLR을 적용해 스택·힙·라이브러리의 시작 주소를 실행마다 무작위로 배치한다.
- 목적: 공격자가 메모리 주소를 예측해 buffer overflow 공격을 하는 것을 방지.
- Linux에서 확인:
cat /proc/sys/kernel/randomize_va_space(2 = 완전 활성화)
정리
- 프로세스 메모리는 텍스트 / 데이터 / BSS / 힙 / 스택으로 구성된다.
- 스택은 함수 호출마다 자동으로 관리되고, 힙은 명시적으로 할당·해제해야 한다.
- 스택은 빠르지만 크기가 제한적이고, 힙은 유연하지만 단편화와 누수 위험이 있다.
- 이 구조를 이해하면 메모리 관련 버그(누수, 오버플로우, dangling pointer)의 원인을 빠르게 파악할 수 있다.