C++/C++ 문법과 핵심 개념

C++ 동적 메모리 할당은 왜 필요할까? - Stack과 Heap

loooong 2026. 8. 27. 10:49

프로그램에서 사용하는 모든 데이터의 크기와 수명을 프로그램 실행 전에 미리 알 수 있는 것은 아니다.

 

예를 들어 게임을 만든다고 생각해보자. 사용자가 선택한 플레이어 수, 런타임에 생성되는 몬스터의 수, 불러온 맵 데이터의 크기처럼 프로그램이 실행된 이후에야 필요한 메모리 크기가 결정되는 경우가 많다.

또 어떤 객체는 특정 함수가 끝난 뒤에도 계속 살아 있어야 할 수 있다.

이러한 데이터를 다루려면 단순히 지역 변수를 선언하는 것만으로는 부족하다.

 

이번 글에서는 왜 동적 메모리 할당이 필요한지, 그리고 이를 이해하기 위해 필요한 Stack과 Heap 메모리의 차이를 정리해본다.

 


동적 메모리 할당이 필요한 이유

 

[1] 정적 크기만으로는 모든 상황을 처리할 수 없다

다음과 같이 배열을 선언했다고 해보자.

int Players[10];

이 배열은 최대 10개의 int를 저장할 수 있다.

 

문제는 실제로 플레이어가 몇 명 필요한지 프로그램을 실행하기 전에는 알 수 없는 경우다.

int Count;
cin >> Count;

int Players[Count]; // 컴파일 에러 - expression did not evaluate to a constant
플레이어 수
→ 사용자 입력에 따라 결정

몬스터 수
→ 게임 진행 상황에 따라 결정

맵 데이터 크기
→ 런타임에 불러온 데이터에 따라 결정

 

이처럼 필요한 데이터의 크기가 런타임(Runtime) 에 결정된다면 처음부터 고정된 크기의 변수만 사용하기 어렵다.

 

물론 예상되는 최대 크기만큼 미리 크게 잡을 수도 있다.

int Players[10000];

하지만 실제로 몇 개가 필요할지도 모르는 상태에서 항상 최대 크기를 확보하는 방식은 불필요한 메모리를 사용할 수 있다.

필요한 만큼의 메모리를 실행 중에 확보할 수 있다면 더 유연하게 데이터를 관리할 수 있다.

이것이 동적 메모리 할당(Dynamic Memory Allocation) 이 필요한 첫 번째 이유다.

 


[2] 객체의 수명을 스코프와 분리해야 할 수도 있다

동적 메모리가 필요한 이유는 크기뿐만이 아니다.

지역 변수는 자신이 선언된 스코프와 수명이 연결되어 있다.

void CreatePlayer()
{
    int HP = 100;
}

HP는 CreatePlayer()가 실행되는 동안 존재하고, 함수가 끝나면 더 이상 사용할 수 없다.

CreatePlayer() 호출
        ↓
HP 생성
        ↓
함수 실행
        ↓
CreatePlayer() 종료
        ↓
HP 수명 종료

 

하지만 실제 프로그램에서는 객체를 함수가 끝난 뒤에도 유지해야 하는 경우가 있다.

예를 들어 어떤 함수에서 플레이어를 생성했지만, 해당 플레이어는 게임이 끝날 때까지 계속 사용해야 할 수 있다.

 

즉, 필요한 것은

원하는 시점에 생성
        ↓
필요한 동안 유지
        ↓
원하는 시점에 해제

할 수 있는 메모리다.

이처럼 객체의 생명주기를 특정 스코프와 분리해야 하는 경우에도 동적 메모리를 사용한다.

 


 

Stack Memory

함수의 지역 변수나 매개변수, 리턴값 등은 일반적으로 Stack Memory와 밀접하게 관련되어 있다.

int Test(int Damage)
{
    int HP = 100;
    HP -= Damage;
    return HP;
}

여기서 Damage, HP와 같은 값은 함수 실행과 함께 사용되고 함수가 끝나면 해당 호출에 속한 스택 영역도 정리된다.

 

Stack의 특징을 간단히 정리하면 다음과 같다.

Stack Memory

주로 저장되는 것
→ 지역 변수, 매개변수, 리턴값 
→ 함수 호출과 관련된 데이터

수명
→ 해당 함수/스코프의 실행과 밀접하게 연결

관리
→ 함수 호출과 반환에 따라 자동으로 처리

 

개발자가 직접

delete HP;

와 같은 작업을 할 필요가 없다.

함수가 종료되면 해당 함수 호출이 사용하던 스택 영역이 자동으로 정리된다.

이 때문에 스택은 사용하기 편하고 메모리 관리 비용도 비교적 작다.

 


 

Heap Memory

Heap은 프로그램 실행 중에 필요한 만큼 메모리를 동적으로 확보할 수 있는 영역이다.

C++에서는 대표적으로 new를 사용해 Heap에 메모리를 할당할 수 있다.

int* Ptr = new int;

 

개념적으로는 다음과 같다.

Stack                   Heap

Ptr ──────────────────► [ int ]

여기서 중요한 점은 Ptr과 Ptr이 가리키는 객체가 서로 다른 위치에 존재할 수 있다는 것이다.

int* Ptr = new int;

일반적인 지역 포인터 변수 Ptr 자체는 현재 함수의 지역 변수이므로 Stack 쪽에서 관리될 수 있다.

반면 new int로 생성된 int 객체는 Heap에 존재한다.

 

즉,

Ptr
→ Heap 객체의 주소를 저장

Heap 객체
→ 실제 동적으로 할당된 데이터

인 구조다.

 


 

Heap 객체의 수명은 스코프와 다르다

Heap에 동적으로 할당한 객체는 해당 포인터 변수가 스코프를 벗어난다고 자동으로 해제되는 것이 아니다.

즉, Heap을 이용하면 객체를 생성한 함수의 스코프와 객체의 생명주기를 분리할 수 있다.

Character* CreateCharacter()
{
    Character* Player = new Character();
    return Player;
}

int main()
{
    Character* Player = CreateCharacter();

    Player->Move();

    delete Player;
    Player = nullptr;
}

CreateCharacter() 함수는 이미 종료되었지만, Heap에 동적으로 생성된 Character 객체의 수명은 종료되지 않는다. 동적으로 할당된 객체는 명시적으로 해제하기 전까지 유지되기 때문이다.

 

Heap은

할당 시점
    ↓
필요한 동안 사용
    ↓
해제 시점

처럼 메모리의 수명을 보다 유연하게 관리할 수 있다는 장점이 있다.

 


 

그렇다면 Heap이 Stack보다 좋은 것일까?

그렇지는 않다. Heap은 유연한 대신 관리 비용이 존재한다.

동적 할당 요청이 발생하면 메모리 관리자는 사용 가능한 영역을 찾아 적절한 메모리 블록을 할당해야 한다.

반면 Stack은 함수 호출과 반환에 따라 일정한 방식으로 메모리를 관리할 수 있기 때문에 일반적으로 할당과 해제가 더 단순하다.

따라서 일반적으로

Stack
→ 빠르고 관리가 단순함

Heap
→ 유연하지만 관리 비용이 더 큼

이라고 볼 수 있다.

Heap을 사용한다고 해서 항상 성능이 나쁜 것은 아니지만, 동적 할당이 공짜는 아니라는 점은 기억해둘 필요가 있다.

특히 짧은 시간 동안 수많은 할당과 해제가 반복되는 프로그램에서는 동적 할당 비용도 고려해야 한다.

 


Heap은 개발자가 수명을 관리해야 한다

Heap의 더 큰 문제는 메모리 관리 책임이다.

다음과 같이 메모리를 할당했다고 해보자.

int* Ptr = new int(100);

사용이 끝났는데도 해제하지 않으면 해당 메모리가 계속 할당된 상태로 남을 수 있다.

int* Ptr = new int(100);

// delete Ptr; 누락

이렇게 더 이상 필요하지 않은 동적 메모리가 적절하게 반환되지 않는 문제를 Memory Leak, 메모리 누수라고 한다.

 

특히 해당 메모리를 가리키던 포인터까지 잃어버리면 문제가 더 커진다.

int* Ptr = new int(100);

Ptr = nullptr;

이제 처음 할당한 메모리는 여전히 Heap에 존재하지만 주소를 잃어버렸기 때문에 정상적으로 해제할 방법도 사라졌다.

Heap

[100]
 ↑
가리키는 포인터가 없음

따라서 동적 메모리를 사용할 때는 단순히

어디서 할당할 것인가?

만 생각할 것이 아니라

누가 이 메모리를 소유하는가?
언제까지 필요한가?
어디서 해제할 것인가?

까지 함께 생각해야 한다.

 


프로그램의 메모리 영역

동적 메모리를 공부하면서 Stack과 Heap 외에도 프로그램이 사용하는 메모리 영역을 함께 정리해두면 이해하기 쉽다.

크게 다음과 같이 생각할 수 있다. 각 영역의 역할을 살펴보자.

Code
Data
Heap
Stack

 


Code 영역

컴파일된 프로그램의 실행 코드, 즉 CPU가 실행할 기계어 명령 등이 저장되는 영역이다.

예를 들어 다음 함수 자체의 실행 명령이 여기에 포함될 수 있다.

void Move()
{
    // ...
}

쉽게 말하면

프로그램이 "무엇을 실행해야 하는지"

에 대한 코드가 저장되는 영역이다.

 


Data 영역

전역 변수와 정적 변수처럼 프로그램 실행 기간 동안 유지되는 데이터가 저장되는 영역이다.

int GlobalValue = 100;

void Test()
{
    static int Count = 0;
}

이러한 변수의 수명은 일반적으로 프로그램의 실행 기간과 함께한다.

프로그램 시작
    ↓
전역/정적 객체 존재
    ↓
프로그램 실행
    ↓
프로그램 종료

세부적으로는 초기화 여부 등에 따라 여러 섹션으로 다시 나뉠 수 있지만, 우선은 전역 변수와 정적 변수가 프로그램 수명 동안 유지되는 영역이라고 이해해두면 된다.

 


Stack 영역

함수 호출 과정에서 필요한 데이터가 관리되는 영역이다.

대표적으로 지역 변수, 매개변수, 리턴값고 같이 함수 호출과 관련된 정보가 여기에 위치할 수 있다.

void Attack(int Damage)
{
    int Result = Damage * 2;
}

함수 호출과 반환에 따라 자동으로 관리된다는 것이 중요한 특징이다.

 


 

Heap 영역

프로그램 실행 중 동적으로 요청한 메모리를 관리하는 영역이다.

Character* Player = new Character();

Heap 객체는 Stack의 지역 변수처럼 함수가 끝났다고 자동으로 없어지는 것이 아니라, 해당 자원의 소유자가 적절하게 수명을 관리해야 한다.

 


 

Stack과 Heap 비교

정리하면 다음과 같다.

중요한 것은 어느 한쪽이 항상 더 좋은 것이 아니라 데이터의 크기와 수명에 맞는 저장 방식을 선택하는 것이다.

구분 Stack Heap
대표 용도 지역 변수, 함수 호출 관련 데이터 동적으로 할당한 객체
크기 결정 일반적으로 함수 실행 구조 및 정적 타입 정보에 따라 관리 런타임에 필요한 크기만큼 요청 가능
수명 스코프 및 함수 호출과 밀접하게 연결 할당부터 해제까지
관리 자동 동적 자원의 소유자가 관리
할당/해제 비용 일반적으로 작음 일반적으로 더 큼
주요 위험 Stack Overflow 등 Memory Leak, 잘못된 해제 등

 


정리

동적 메모리 할당이 필요한 이유는 크게 두 가지로 정리할 수 있다.

첫 번째는 런타임에 필요한 메모리 크기가 결정되는 데이터를 처리하기 위해서다.

두 번째는 객체의 생명주기를 현재 함수나 스코프와 분리해 관리하기 위해서다.

 

이를 위해 Heap 메모리를 사용할 수 있다.

하지만 Heap은 Stack보다 유연한 대신 동적 할당과 해제에 추가적인 관리 비용이 들고, 수명 관리에 실패하면 메모리 누수와 같은 문제가 발생할 수 있다.

 

따라서 동적 메모리를 공부할 때는 단순히 "new로 메모리를 만든다"라고 이해하기보다,

왜 동적으로 만들어야 하는가?
누가 소유하는가?
언제까지 살아 있어야 하는가?
언제 해제해야 하는가?

를 함께 생각하는 것이 중요하다.