C# 메모리 관리 – 참조 타입과 힙 Reference Types and the Heap
C# 메모리 관리
참조 타입과 힙 Reference Types and the Heap
참조 타입 변수는 참조 값이 메모리에 저장되는 객체입니다. 클래스의 인스턴스를 새로 생성할 때, 그 데이터가 힙 heap에 저장되고, 저장된 데이터의 위치를 가리키는 참조 값도 저장됩니다. 참조란 객체의 주소를 값으로 저장하는 변수를 말합니다. 클래스의 인스턴스를 생성하기 위해서 new키워드를 사용하는데, new 키워드는 객체 타입에 해당하는 메모리 공간과 객체를 초기화하는데 필요한 코드의 수행을 OS에 요청합니다. Dog 타입의 객체 초기화에 대한 코드를 살펴보겠습니다.
public class Dog { public string name { get; set;} public int age { get; set;} public Dog(string name, int age) { this.name = name; this.age = age; } } public class Test : MonoBehaviour { Dog dog1 = new Dog("Rufus",10); Dog dog2 = new Dog("Brutus",8); private void Update () { if(Input.GetKeyDown(KeyCode.Alpha1)) Debug.Log ("The dog1 is named "+dog1.name+" and is "+dog1.age); if(Input.GetKeyDown(KeyCode.Alpha2)) Debug.Log ("The dog2 is named "+dog2.name+" and is "+dog2.age); } }
dog1과 dog2 변수는 참조 변수로서 각 객체들이 저장된 메모리 위치 정보를 저장합니다.
Instantiate 함수를 이용해서 동작하는 코드를 살펴보겠습니다. Enemy라고 불리는 프리팹(prefab)이 있고, 이 프리팹이 3D 모델, 컴포넌트 몇개 그리고 스크립트 몇개를 포함하고 있다고 가정해 보겠습니다.
using UnityEngine; using System.Collections; public class Test : MonoBehaviour { public GameObject enemyPrefab; private void Update() { if(Input.GeyKeyDown(KeyCode.Space)) Instantiate(enemyPrefab,new Vector3(0,0,0), Quaternion.identity); } }
enemyPrefab의 인스턴스를 새로 생성했고 동작하고 있습니다. 문제는 생성된 인스턴스를 참조하는 변수가 없기 때문에 인스턴스에 접근할 수 없다는 점 입니다. 이를 위해서 새로 생성된 객체에 접근을 위한 조치를 취해야 합니다. 변수를 생성하고 이 변수를 이용해서 객체를 참조할 수 있습니다.
void Update() { if(Input.GeyKeyDown(KeyCode.Space)) { GameObject enemyRef = Instantiate(enemyPrefab, new Vector3(0,0,0), Quaternion.identity); enemyRef.AddComponent(Rigidbody); Destroy(enemyRef); } }
이 예제에서는 enemyRef 변수가 선언되고 새로 생성된 객체의 주소가 할당되었습니다. enemyRef는 이제 새로 생성된 객체를 참조합니다. enemyRef 변수가 객체의 위치를 알고 있기 때문에 t 컴포넌트를 추가했듯이, 컴포넌트를 추가하고 객체의 public 변수에 접근할 수 있습니다. 마지막 줄에서 객체가 삭제되었습니다. 사실 이런 방식으로 처리하는 것이 옳지는 않지만 설명을 위해서 예제를 이렇게 준비했습니다.
위의 예제에서 enemyRef 참조 변수는 if 구문의 중괄호 { } 에 위치하는 자동 변수 라는 점을 명심하시기 바랍니다. 이 영역 밖에서는 enemyRef 참조 변수가 더 이상 존재하지 않습니다. enemy 객체에 대한 정보를 얻기 위해서는 객체를 찾아야 합니다. 예를 들어서 아래 코드와 같이 검색을 할 수 있습니다:
GameObject enemyRefAgain = GameObject.Find("Enemy");
모든 참조 타입 변수들은 힙 heap에 저장됩니다. 다음 타입들이 참조 타입입니다:
- class
- object
- string (string은 변수와 비슷하게 생겼지만 String 클래스의 인스턴스 객체를 참조합니다)
값 타입 value type 변수와 참조 타입 reference type 변수 사이의 가장 중요한 차이는 참조 타입 변수는 해당 데이터가 저장된 위치를 가리킨다는 점입니다. 참조 변수는 참조 변수가 가리키는 데이터를 사용하기 전에 먼저 데이터가 저장된 위치에 접근해야 합니다. 바로 이 점이 바로 접근이 가능한 구조체보다 클래스의 속도가 조금 느리게 하는 점입니다.
다른 중요한 차이점은 다음과 같은 코드가 동작할 때 확인할 수 있습니다:
using UnityEngine; using System.Collections; public class Memory : MonoBehaviour { DogC dogC1 = new DogC(); DogC dogC2 = new DogC(); int number1; int number2; private void Update () { if(Input.GetKeyDown(KeyCode.Space)) { number2=number1 = 10; number1 = 20; Debug.Log (number1+" "+number2); dogC1.name = "Rufus"; dogC1.age = 12; dogC2 = dogC1; dogC1.name = "Brutus"; Debug.Log (dogC1.name+" "+dogC1.age+" "+dogC2.name+" "+dogC2.age); } } }
예제 코드에서 두 개의 숫자는 독립적이기 때문에 둘 중 하나의 숫자를 변경해도 다른 숫자에는 영향이 없다는 점을 알 수 있습니다. 반면에, 특정 클래스를 다른 클래스에 할당하게 되면, 둘 중 하나를 변경하면 다른 클래스에도 영향을 미치게 됩니다.
이렇게 동작하는 이유는 dogC2 = dogC1; 코드가 실행되면, dogC1의 주소를 dogC2 참조 변수에 할당하게 되기 때문입니다. dogC2의 원래 주소는 사라지고 dogC1과 dogC2의 주소가 같아집니다. 따라서 같은 데이터 위치를 가리키는 두 개의 참조 변수가 생기게 됩니다.