CS/운영체제

메모리 구조(Memory Structure)

glorypang 2025. 6. 23. 10:06
728x90
반응형
SMALL

0. 우리가 프로그램을 실행할 때,

우리가 코드를 짜고 `실행(run)` 버튼을 눌렀을 때,

그 순간 무슨 일이 일어날까요?

사실 우리가 만든 프로그램은 그 자체로는 그냥 텍스트 파일일 뿐이에요.

코드 자체만으로는 컴퓨터가 이해하거나 실행할 수 없습니다.

그러면 컴퓨터는 어떻게 이걸 “실행”하는 걸까요?

프로그램이 동작하려면?

  1. 먼저, 코드가 CPU가 이해할 수 있는 언어(기계어)로 변환(컴파일 또는 인터프리팅)되어야 합니다.
  2. `실행` 버튼을 누르면 → 이 기계어가 RAM(주기억장치)에 로딩 올라갑니다.
  3. 그제야 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)` 같은 무리한 요청
메모리 해제를 안 하고 계속 할당 누수가 누적되어 힙이 팽창
잘못된 포인터 연산 스택 영역까지 침범할 수 있음
728x90
반응형
LIST