반응형

값 형식과 참조 형식은 변수가 어떤 데이터를 담는지에 따라 구분이 됩니다.

 

그렇다면 앞서 언급했던 변수란 무엇일까요?

변수란 코드에서 보자면 값을 대입시켜 변화시킬 수 있는 요소이지만, 메모리 관점에서 보면 '데이터를 담는 일정 크기의 공간'이라는 의미를 갖기도 합니다.

 

그래서 값 형식은 변수가 값을 담는 데이터 형식이고, 참조 형식은 변수가 값 대신 값이 있는 곳의 위치(주소)를 담는 데이터 형식입니다.

 

그렇다면 왜 값 형식과 참조 형식을 나눠서 쓰게 된걸까요?

 

값 형식의 지역 변수는 스택 프레임에서 관리될 수 있습니다. 다만 값 형식이라고 해서 항상 스택에 저장되는 것은 아니며, 여기서는 스택의 동작을 이해하기 위해 지역 변수를 예시로 들어보겠습니다.

void Test()
{
    int a = 1;
    int b = 2;
    int c = 3;
}
// 하나의 Stack Frame
    c : 3
  b : 2 b : 2
a : 1 a : 1 a : 1

위 예시에서는 메서드가 실행되면서 지역 변수 a, b, c가 Stack Frame에서 관리됩니다. 이를 단순하게 표현하면 a부터 b, c 순으로 스택에 쌓이는 형태로 볼 수 있습니다. 스택은 LIFO 구조를 사용하며, 메서드 호출 시 생성된 Stack Frame은 메서드 반환 시 회수됩니다. 위 표는 스택의 구조를 이해하기 위한 단순화된 표현입니다.

 

참조 형식의 객체는 일반적으로 관리 힙에 생성되며, 변수에는 해당 객체를 가리키는 참조가 저장됩니다.

힙 메모리 영역은 저장된 데이터를 스스로 제거하는 기능을 가지고 있지 않습니다. 대신에 가비지 컬렉터(GC)가 힙에 더 이상 사용하지 않는 객체를 쓰레기로 간주하여 수거하는 기능을 합니다. 

void Test()
{
    object a = 1;
    object b = 2;
    
    // ※ int와 같은 값 형식을 object에 대입하면 실제로는 박싱(Boxing)이 발생합니다. 박싱은 이후 별도로 다루겠습니다.
}
// 하나의 Stack Frame
    메모리 주소
    1000
    2000
b: &3000 2 3000
a: &4000 1 4000
스택 힙  

 

위와 같이 스택에는 힙에서 생성된 값을 가리키는 메모리 주소 값이 저장됩니다. 메서드 호출이 끝나면 해당 지역 변수의 참조는 사라지고, 다른 곳에서도 해당 데이터를 참조하고 있지 않다면 이후 가비지 컬렉터가 해당 데이터를 찾아 회수하게 됩니다.

 

이렇게만 보면 "어차피 메서드가 끝나면 지역 변수는 사라지는데, 값 형식과 참조 형식을 굳이 왜 나눠서 사용하는 걸까?"라는 생각이 들 수 있습니다.

 

그래서 값 형식과 참조 형식의 변수가 복사를 하게 되면 어떻게 적용되는지 알아보겠습니다.

// 값 형식
void Test1()
{
    int a = 10;
    int b = a;

    b = 20;

    Debug.Log(a); // 10
    Debug.Log(b); // 20
}

// 참조 형식

class Player
{
    public int hp;
}

void Test2()
{
    Player a = new Player();
    a.hp = 100;

    Player b = a;

    b.hp = 50;

    Debug.Log(a.hp); // 50
}

위 예시처럼 값 형식의 경우에는 값 자체가 복사 되기 때문에 a와 b 변수는 서로 별개의 값입니다.

반면에 참조 형식의 경우에는 b = a에서 Player 객체를 복사한게 아니라 참조를 복사했기 때문에 a와 b가 참조하고 있는 객체가 동일해지게 됩니다. 따라서 b.hp를 50으로 변경했을 때 a.hp도 50이 나오게 됩니다.

 

따라서 값 형식과 참조 형식의 차이점은

1. 값 형식과 참조 형식은 무엇을 저장하는가?
→  값 형식은 값 자체를 저장하고, 참조 형식은 값을 가리키는 메모리 주소를 저장한다

2. 값 형식과 참조 형식은 어디에 저장되는가?
→ 값 형식 = 스택, 참조 형식 = 힙으로 단순하게 구분할 수 없다. 실제 저장 위치는 해당 값이 지역변수인지, 객체의 필드인지 등의 상황에 따라 달라진다.

3. 스택과 힙은 어떻게 관리되는가?
→  스택은 일반적으로 메서드 호출 단위의 Stack Frame으로 관리되며 메서드 반환 시 해당 프레임을 더 이상 사용하지 않는다. 힙은 가비지 컬렉터가 더 이상 참조되지 않는 데이터를 찾아 회수한다.

4. 복사할 때 어떤 차이가 있는가?
→  값 형식은 값 자체가 복사되어 서로 독립적인 값을 가지게 되고, 참조 형식은 참조가 복사되므로 두 변수가 동일한 객체를 가리킬 수 있다.

 

정리하자면

값 형식은 값 자체를 가지고 있고, 참조 형식은 데이터가 있는 메모리 주소를 가지고 있습니다.
그래서 복사를 하게 되면 값 형식은 값이 그대로 복사돼 서로 독립적으로 동작하고, 참조 형식은 주소가 복사돼 같은 데이터를 바라보게 됩니다.
메모리 측면에서는 스택은 메서드가 끝나면 Stack Frame 단위로 정리되고, 힙에 남아 있는 데이터는 더 이상 참조되지 않을 때 GC가 회수합니다.


다만 값 형식과 참조 형식을 단순히 스택과 힙으로만 구분할 수는 없습니다. (예를 들어 클래스 안의 int 필드는 값 형식이지만 힙에 생성된 객체 안에 존재할 수 있고, 반대로 클래스 타입의 지역 변수는 실제 객체는 힙에 있지만 그 객체를 가리키는 참조값은 Stack Frame에서 관리될 수 있습니다.)

반응형

'TIL' 카테고리의 다른 글

CS - Object와 박싱/언박싱  (0) 2026.09.24
Unity 2D - 무기 시스템 (전략 패턴)  (0) 2026.04.23