박싱 언박싱
처음 프로그래밍을 배울 때, 박싱과 언박싱에 대해서 가볍게 배운 적이 있다.
배운 이후 사용을 잘 안 하기도 했고, 면접 준비도 할 겸 CS 지식들을 리마인드 해주면 좋을 것 같아, 배우던 도중 단순히 박싱 언박싱의 복사를 해 참조형으로 만드는 기능적인 부분뿐만 아니라, 메모리 적인 단점을 알게 되어 정리해서 기록을 해 두고자 글을 쓴다.
먼저 박싱과 언박싱은, 값 형식과 참조 형식 간에 일어나는 일로,
박싱은 값 형식을 참조형식으로 변환하는 과정이다.
이때 스택에 있는 값 형식을 똑같이 복사해서 힙에 새로운 객체로 저장되고 그 참조가 object 변수에 저장이 되는 과정이다.
반대로 언박싱은 박싱된 참조 형식을 다시 값 형식으로 변환하는 과정으로
object가 가리키는 힙 메모리의 주소에 접근해 그 안의 값을 꺼내서 다시 스택에 복사해 저장하는 방식이다.
이때까지는 박싱과 언박싱이라는 머릿속 이미지에 막연하게 동일한 하나의 물건이 값 형식이 되었다, 참조 형식이 되었다를 한다고 생각했지만, 실제로는 추가적인 메모리 할당과 복사가 일어나는 것을 알 수 있었다.
이게 무슨 뜻이냐면.
int number = 42;
object boxed = number; // 박싱
int number2 = (int)boxed; // 언박싱
이러한 과정에서 number 로서 저장된 int 값이 존재하고, 박싱을 통해 number를 object로 박싱을 해도, 실제로는 int 값으로 스택에 존재하는 number와 number를 복사해서 참조로 저장한 object, 총 2개가 각각 다른 메모리에 할당된다는 뜻이다.
때문에 number의 값을 바꾸거나 object의 값을 바꿔도 영향이 없고, 같은 값을 저장하면서 2배로 메모리를 차지하기 때문에 성능 저하의 원인이 될 수 있다는 단점이 있다.
참고로 이러한 박싱은 유니티에서도 일어날 수 있는데
컬렉션에 값 형식을 넣을 때
List<object> list = new List<object>();
int number = 10;
list.Add(number); // 박싱 발생
int value = (int)list[0]; // 언박싱 발생
Event System을 사용 시
Unity 이벤트 시스템(UnityEvent)에 값형식을 넘기면 내부에서 object로 처리하면서 박싱
UnityAction<int> myAction;
private void Start()
{
myAction += (num) => { Debug.Log(num); };
int number = 10;
myAction.Invoke(number); // 박싱 가능성 있음
}
ㄴ 이때 유니티 이벤트가 아니라 Func 나 Action으로 값 타입을 제네릭 타입 파라미터로 받으면 박싱이 일어나지 않는다.
와 같은 케이스가 존재한다.