C# 메모리 관리 – 구조체 vs 클래스
C# 메모리 관리 – 구조체와 클래스
구조체 vs 클래스 Struct vs Class
상황에 따라서 어떤 걸 사용해야 할까요? 클래스를 사용하면 변수가 하나 더 추가된다는 것을 확인했습니다 (참조를 하기 위한 변수). 따라서 객체를 수천개 생성한다면 수천 개의 변수가 추가되지만, 구조체는 추가되는 변수가 없습니다. 아래 예제를 살펴보겠습니다:
public class DogC { public string name { get; set;} public int age { get; set;} } public struct DogS { public string name; public int age; } using UnityEngine; using System.Collections; public class Memory : MonoBehaviour { DogC dogC = new DogC(); DogS dogS = new DogS(); void Update () { if(Input.GetKeyDown(KeyCode.Alpha1)) Print (dogS); if(Input.GetKeyDown(KeyCode.Alpha2)) Print (dogC); if(Input.GetKeyDown(KeyCode.Alpha3)) print ("Struct: The dog's name is "+dogS.name +" and is "+dogS.age); if(Input.GetKeyDown(KeyCode.Alpha4)) print ("Class: The dog's name is "+dogC.name +" and is "+dogC.age); } void Print(DogS d) { d.age = 10; d.name = "Rufus"; print ("Struct:The dog's name is "+d.name +" and is "+d.age); } void Print(DogC d) { d.age = 10; d.name = "Rufus"; print ("Class:The dog's name is "+d.name +" and is "+d.age); } }
우선, 세 번째 입력은 구조체 Dog의 name과 age 값이 사라져, 예상한 대로 출력되지 않는 것을 확인할 수 있습니다. 반면에 클래스는 그 값이 유지됩니다.
구조체는 값 타입 value type 이고 클래스는 참조 타입 reference type 이라고 설명했던 것을 기억하실 겁니다. 함수를 호출해서 구조체를 전달하면 구조체가 복사되어 전달되지만, 복사된 데이터는 원본 데이터가 아닙니다. 따라서 복사된 값을 수정하면, 스택에 복사된 값을 변경하게 되는 것입니다. 따라서 복사본에 입력된 값들은 함수가 끝나면서 사라지게 됩니다.
클래스는 참조를 이용해서 전달이 되기 때문에 원본 데이터에 접근이 가능하고 함수 내부에서 원본 데이터를 수정할 수 있습니다. 그리고 변경된 데이터들은 함수가 종료되어도 남아있게 됩니다.
여기에서 생각해야할 것이 있습니다. 어떤 구조체가 10개의 변수를 가진 크기가 큰 구조체라고 할 때, 이 구조체를 함수에 전달하면 스택의 위치가 10만큼 늘어나게 됩니다. 이제 이 구조체를 50개 포함하고 있는 배열을 함수에 전달하면, 스택 위치가 500 위치만큼 커지게 됩니다. 스택은 크기가 제한적이기 때문에 구조체가 더 커지거나 배열이 더 커지게 되면 스택 오버플로우가 발생할 수도 있습니다.
반면에 클래스의 참조를 전달하면, 하나의 변수만 전달하게 되는 것입니다. 50개의 클래스 인스턴스를 전달하면 50개의 변수만 스택에 추가되기 때문에 그 만큼만 크기가 늘어납니다.
정리해보면, 클래스는 메모리를 절약하고 구조체는 빠릅니다. 따라서 구조체와 클래스의 특징을 잘 이해한 뒤 균형을 맞추는 것이 중요합니다. 예를 들어서 변수가 5에서 8개 보다 작은 경우에는 구조체를 사용하는 것을 고려해보고, 그보다 많으면 클래스를 사용합니다.
또한 구조체 내의 변수에 접근할 때마다 (예. transform.position) 그 구조체의 복사본이 스택에 생성된다는 것을 명심해야 합니다 – 복사본은 생성할 때 시간이 걸립니다.
클래스의 인스턴스를 생성할 때 마다 힙에 메모리를 할당하기 때문에 값을 폐기하기 위해서는 가비지 컬렉션이 필요합니다 – 구조체는 스택에 할당되기 때문에 가비지 컬렉션이 발생하지 않습니다.
유니티는 3개에서 4개 정도의 변수를 가지는 경우 구조체를 사용합니다. position, color, quaternion, rotation, scale 등이 모두 구조체 입니다.
수명이 짧고, 자주 할당되는 객체는 가비지 컬렉션을 발생시키지 않기 때문에 구조체를 사용하기에 좋은 후보군 입니다. 수명이 길고, 크기가 큰 객체는 해당 객체에 접근할 때 복사되지 않고 참조가 유지된다면 가비지 컬렉션이 발생하지 않기 때문에, 클래스를 사용하기에 좋은 후보군입니다. Vector3와 RaycastHit와 같이 유니티에서 크기가 작은 객체들 대부분이 이런 이유로 구조체로 구현되어 있습니다.
다음 내용으로 넘어가기전에 한 가지를 더 살펴보겠습니다. 구조체를 참조 형태로 전달하는 것이 가능하고, 이를 이용해서 구조체의 원본 값을 수정하고 수정한 값을 유지하는 것이 가능합니다.
void PrintRef(ref DogS d) { d.age = 10; d.name = "Rufus"; print ("Structure:The dog's name is "+d.name +" and is "+d.age); }
위의 함수는 아래와 같은 방식으로 호출합니다.
void Update() { PrintRef(ref dogS); Debug.Log ("Structure:The dog's name is "+dogS.name +" and is "+dogS.age); }
ref 키워드를 발견하셨을텐데, 이 키워드는 컴파일러에게 구조체를 전달할 때 스택에 구조체를 복사하는 대신, 구조체를 참조하는 참조 변수를 생성하도록 합니다. 따라서 함수가 종료되어도, 함수 내부에서 설정된 값이 구조체에 유지됩니다. 이 키워드는 모든 값 타입 value type에 사용 가능합니다.
내용 끝까지 읽어주셔서 감사합니다.
배너 클릭은 저에게 많은 힘이 됩니다.
감사합니다 🙂
너무 좋은 글 잘읽고 있습니다.
특히 개념뿐만아니라 실사용에서 어떻게 적용되는가 공부하면서 너무 이해가 잘되고 있습니다.
정말 감사합니다.
도움이 되셨다니 기쁘네요.
블로그 방문 감사드립니다 🙂
수명이 길다는게 어떤 의미인가요ㅜ?
오래 사용되는 데이터를 말합니다.
짧게 사용하면 수명이 짧은 것이고, 오랫 동안 유지하면서 사용하면 수명인 긴 것이지요.
블로그 방문 감사합니다 🙂