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도 모두 변경됨!
}
}
주의사항
- 참조 형식은 메모리(memory) 주소를 공유하므로, 한 곳에서 변경하면 다른 곳도 영향받음
- 값 형식은 값 자체를 복사하므로, 각각 독립적으로 동작
- Unity에서
GameObject는 참조 형식이므로 주의해서 사용