값 형식과 참조 형식 (Value Type & Reference Type)

C#의 값 형식과 참조 형식을 학습합니다. Stack과 Heap 메모리의 차이와 데이터가 저장되고 복사되는 방식을 이해합니다.

15. 값 형식과 참조 형식 (Value Type & Reference Type)

메모리(memory) 구조 이해하기

C#에서는 데이터를 저장하는 방식에 따라 **값 형식(Value Type)**과 **참조 형식(Reference Type)**으로 나뉩니다.

Stack과 Heap

Stack (스택)

  • Last In First Out (LIFO): 마지막에 들어온 것이 먼저 나감
  • 값 형식 데이터가 저장되는 곳
  • 빠른 접근 속도
  • 작은 크기의 데이터에 적합

Heap (힙)

  • 참조 형식 데이터가 저장되는 곳
  • 동적으로 크기가 변할 수 있는 데이터
  • 메모리(memory) 주소(참조)를 통해 접근

값 형식 (Value Type)

값 형식은 Stack에 저장되며, 데이터(값)를 직접 저장하는 방식입니다.

값 형식의 특징

  • 데이터를 직접 복사
  • 변수(variable)에 값을 할당하면 값 자체가 복사됨
  • 한 변수(variable)를 변경해도 다른 변수(variable)에 영향 없음

값 형식 예시

int a = 5;
int b = a;  // b에 a의 값(5)이 복사됨

b = 10;     // b만 10으로 변경

Debug.Log(a);  // 5 출력 (a는 변경되지 않음)
Debug.Log(b);  // 10 출력

Unity에서 값 형식

  • Vector3 (구조체(struct))
  • int, float, bool 등 기본 타입(type)
  • 구조체(struct)
Vector3 position1 = new Vector3(1, 2, 3);
Vector3 position2 = position1;  // 값이 복사됨

position2.x = 10;  // position2만 변경됨
// position1은 여전히 (1, 2, 3)

참조 형식 (Reference Type)

참조 형식은 Heap에 저장되며, 메모리(memory) 주소(참조)를 통해 접근하는 방식입니다.

참조 형식의 특징

  • 메모리(memory) 주소를 저장
  • 변수(variable)에 할당하면 같은 메모리(memory) 주소를 참조
  • 한 변수(variable)를 변경하면 다른 변수(variable)도 영향받음

참조 형식 예시

int[] myArray = { 1, 2 };
int[] myArray2 = myArray;  // 같은 메모리 주소를 참조

myArray2[1] = 7;  // myArray2를 변경

Debug.Log(myArray[1]);   // 7 출력 (myArray도 변경됨!)
Debug.Log(myArray2[1]);  // 7 출력

이유: 두 배열(array)이 같은 메모리(memory) 주소를 바라보고 있기 때문입니다.

Unity에서 참조 형식

  • GameObject (클래스(class))
  • string
  • 배열 (int[], string[])
  • 리스트 (List<T>)
  • 클래스(class)
GameObject obj1 = new GameObject("Player");
GameObject obj2 = obj1;  // 같은 객체를 참조

obj2.name = "Enemy";  // obj2의 이름 변경
// obj1.name도 "Enemy"로 변경됨!

new 키워드의 차이

값 형식에서의 new

Vector3 pos = new Vector3(1, 2, 3);
  • Stack에 값을 생성
  • 단순히 초기값을 설정하는 역할

참조 형식에서의 new

GameObject obj = new GameObject();
  • Heap에 객체(object)를 생성
  • 메모리(memory) 할당과 초기화를 수행

값 형식 vs 참조 형식 비교

특징 값 형식 (Value Type) 참조 형식 (Reference Type)
저장 위치 Stack Heap
저장 내용 값 자체 메모리(memory) 주소 (참조)
복사 방식 값 복사 참조 복사
변경 영향 독립적 공유됨
예시 int, Vector3 GameObject, string[]

실전 활용 예시

값 형식 활용 (Vector3, int 등)

public class PlayerMovement : MonoBehaviour
{
    // 플레이어 위치 저장 (값 형식)
    Vector3 playerPos = new Vector3(0, 0, 0);
    Vector3 savedPos = playerPos;  // 값 복사 (독립적인 복사본)
    
    void Start()
    {
        playerPos.x = 10;  // playerPos만 변경
        // savedPos는 여전히 (0, 0, 0) - 독립적!
        
        Debug.Log("playerPos: " + playerPos);      // (10, 0, 0)
        Debug.Log("savedPos: " + savedPos);        // (0, 0, 0)
    }
    
    // 체력도 값 형식
    int playerHp = 100;
    int maxHp = 100;
    
    void SaveMaxHp()
    {
        maxHp = playerHp;  // 값 복사
        playerHp = 50;      // playerHp만 변경
        // maxHp는 여전히 100
    }
}

참조 형식 활용 (GameObject, List 등)

public class GameManager : MonoBehaviour
{
    // 여러 스크립트가 같은 리스트 공유 (참조 형식)
    List<int> scores = new List<int>() { 100, 200 };
    List<int> highScores = scores;  // 같은 리스트 참조 (같은 메모리 주소)
    
    void Start()
    {
        highScores.Add(300);  // highScores에 추가
        // scores에도 300이 추가됨! (같은 리스트를 참조하므로)
        
        Debug.Log("scores: " + string.Join(", ", scores));      // 100, 200, 300
        Debug.Log("highScores: " + string.Join(", ", highScores));  // 100, 200, 300
    }
}

public class EnemyManager : MonoBehaviour
{
    // GameObject는 참조 형식
    GameObject enemy1 = new GameObject("Enemy1");
    GameObject enemy2 = enemy1;  // 같은 GameObject 참조
    
    void Start()
    {
        enemy2.name = "Boss";  // enemy2의 이름 변경
        // enemy1.name도 "Boss"로 변경됨! (같은 객체를 참조하므로)
        
        Debug.Log("enemy1.name: " + enemy1.name);  // "Boss"
        Debug.Log("enemy2.name: " + enemy2.name);  // "Boss"
    }
}

게임 개발에서의 실제 활용

값 형식 사용 예시:

public class Player : MonoBehaviour
{
    // 값 형식: 각각 독립적으로 저장
    Vector3 position = Vector3.zero;
    int hp = 100;
    float speed = 5.0f;
    
    void SaveCheckpoint()
    {
        Vector3 checkpointPos = position;  // 값 복사
        int checkpointHp = hp;            // 값 복사
        
        // 나중에 체크포인트로 복귀
        position = checkpointPos;  // 독립적인 값
        hp = checkpointHp;          // 독립적인 값
    }
}

참조 형식 사용 예시:

public class Inventory : MonoBehaviour
{
    // 참조 형식: 여러 곳에서 같은 리스트 공유
    List<string> items = new List<string>();
    
    void Start()
    {
        List<string> playerItems = items;  // 같은 리스트 참조
        
        items.Add("검");
        // playerItems에도 "검"이 추가됨! (같은 리스트)
        
        playerItems.Add("방패");
        // items에도 "방패"가 추가됨! (같은 리스트)
    }
}

public class ItemManager : MonoBehaviour
{
    // GameObject 참조: 여러 변수가 같은 오브젝트 참조
    GameObject currentWeapon;
    GameObject equippedWeapon;
    
    void EquipWeapon(GameObject weapon)
    {
        currentWeapon = weapon;      // 같은 GameObject 참조
        equippedWeapon = weapon;     // 같은 GameObject 참조
        
        weapon.name = "Equipped Sword";  // weapon의 이름 변경
        // currentWeapon.name과 equippedWeapon.name도 모두 변경됨!
    }
}

주의사항

  1. 참조 형식은 메모리(memory) 주소를 공유하므로, 한 곳에서 변경하면 다른 곳도 영향받음
  2. 값 형식은 값 자체를 복사하므로, 각각 독립적으로 동작
  3. Unity에서 GameObject는 참조 형식이므로 주의해서 사용

← 목차로 돌아가기