[Daily morning study] 프로세스 메모리 구조 — 스택, 힙, 텍스트, 데이터 세그먼트

#daily morning study

Image


프로세스 메모리 구조란

운영체제는 프로세스를 실행할 때 각 프로세스에게 독립된 가상 주소 공간(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)의 원인을 빠르게 파악할 수 있다.