Boxing과 Unboxing

C# 2008. 12. 11. 20:26

개 요

 

이번에는 닷넷의 특징이라고 할 수 있는 Boxing, Unboxing에 대해 배워보도록 하겠습니다.

 

내부적으로 일어나는 일이기때문에 처음에는 몰라도 상관없지만 퍼포먼스에 좀 더 신경쓰기 시작하면 반드시 이해해야할 개념이라 할 수 있습니다.

 

MSIL 코드를 직접 보면서 Boxing, Unboxing 을 이해해 봅니다.

 

 

Boxing

 

Value 타입의 변수는 Stack에 생성되고 Reference 타입의 변수는 Heap에 생성됩니다.

 

Heap에 무엇인가가 생성될 때 변수는 이 heap에 생성된 객체를 가르키는 4byte "포인터(주소값)"를 할당받게 됩니다.

 

그래서 이 변수를 다른 함수의 매개 변수로 넘길 때는 항상 객체 자체가 아니라 이 포인터만 넘기게 됩니다.

 

value 타입의 변수 경우는 어떤 함수의 매개 변수가 될 경우 이 값의 copy를 만들고 copy를 통째로 넘겨주게 됩니다.

 

여기까지는 c++ 기반 개발자라면 이미 누구나 알고 있는 사항일 것입니다.

 

그러나 닷넷에선 Value 타입의 변수를 Reference 타입의 변수인것처럼 사용하는 경우가 있습니다.

 

그리고 바로 이 때 "Boxing"이 이루어집니다.

 

자 다음과 같은 예를 들어보도록 하겠습니다.

 

public void Method()

{
         ArrayList ar = new ArrayList();

         ar.Add(5);

}

 

ArrayList.Add() 함수의 매개변수로 요구하는 것은 분명 Reference 형인 Object입니다.

 

그런데 value 타입인 integer 값을 함수의 매개 변수로 사용하고 있군요.

 

바로 위와 같은 상황처럼 Value 타입의 변수를 Reference 타입인양 사용할 경우에 Boxing이 일어납니다.

 

IL코드를 확인해 볼까요?

 

.method public hidebysig instance void Method() cil managed

{

     // Code Size: 20 byte(s)

     .maxstack 2

     .locals (

     [mscorlib]System.Collections.ArrayList list1)

     L_0000: newobj instance void [mscorlib]System.Collections.ArrayList::.ctor()

     L_0005: stloc.0

     L_0006: ldloc.0

     L_0007: ldc.i4.5

     L_0008: box int32

     L_000d: callvirt instance int32 [mscorlib]System.Collections.ArrayList::Add(object)

     L_0012: pop

     L_0013: ret

}

 

AL_0007에서 4바이트 정수 5를 스택으로 로드하고 있습니다.

 

그리고 다음줄에서 우리가 기다리던 boxing을 하는군요.

 

ArrayList에 실제로 저장되는 값은 뭘까요?

 

앞에서 말씀드린 바와 같이 Add()함수의 매개 변수는 Object이기 때문에 5라는 값이 아니라 4바이트의 주소 포인터를 저장해야 한다는 것을 우리는 이미 알고 있습니다.

 

integer object로 변환되는 과정이 필요하겠군요.

 

integer가 가지고 있던 5라는 ''은 힙에 생성한 새로운 주소 공간으로 copy되고 그 값이 저장된 주소를 리턴받습니다.

 

value 타입의 integer(int32) reference 타입의 object가 변환되었습니다.

 

이렇게 닷넷에서 우리가 모르는 사이에 value 타입의 변수를 reference처럼 사용할 수 있도록 해주는 것을 boxing이라고 합니다.

 

 

Unboxing

 

자 그럼 이제 Unboxing 에 대해 알아보도록 하겠습니다.

 

Unboxing은 간단히 boxing의 반대 과정이라고 보시면 됩니다.

 

public void Method()       

{           

ArrayList ar = new ArrayList();

ar.Add(5);

int b;

b = (int)ar[0];

}

 

Unboxing이란 object에 저장된 주소값으로부터 ''을 얻어다 스택에 생성해둔 변수 b copy하는 과정입니다.

 

마찬가지로 IL 코드에 unbox라고 나오는걸 보실수 있습니다.

 

.method public hidebysig instance void Method() cil managed

{

     // Code Size: 34 byte(s)

     .maxstack 3

     .locals (

     [mscorlib]System.Collections.ArrayList list1, int32 num1)

     L_0000: newobj instance void [mscorlib]System.Collections.ArrayList::.ctor()

     L_0005: stloc.0

     L_0006: ldloc.0

     L_0007: ldc.i4.5

     L_0008: box int32

     L_000d: callvirt instance int32 [mscorlib]System.Collections.ArrayList::Add(object)

     L_0012: pop

     L_0013: ldloc.0

     L_0014: ldc.i4.0

     L_0015: callvirt instance object [mscorlib]System.Collections.ArrayList::get_Item(int32)

     L_001a: unbox int32

     L_001f: ldind.i4

     L_0020: stloc.1

     L_0021: ret

}

 

쉽게 이해가 가시리라고 봅니다. :)

 

 

정리

 

boxing unboxing을 가능하면 피하는게 좋다는 말을 한번쯤 들어보셨을 겁니다.

 

위에서 보신것처럼 boxing, unboxing이란 닷넷의 배려는 우리가 편하게 프로그램을 짤 수 있도록 했는지는 모르지만

 

IL 코드에서 확인할 수 있는 것처럼 더 많은 과정을 거치고, 메모리를 사용하고, 속도가 느려지게끔하는 효과를 가져올 수 있습니다.

 

boxing하는 과정에서 값을 copy하기 위해 힙에 주소 공간을 확보해야 한다는 것을 기억하실 겁니다.

 

잦은 boxing은 사용되지 않는 리소스를 정리하기 위해 비용이 큰 Garbage Collector가 동작하는 주기를 더 짧아지게 할 수 있습니다.

 

그렇다면 불필요한 boxing을 막으려면? boxing이란 결국 value type의 변수를 reference type object로 암시적으로 변환하는 것입니다.

 

만약 객체가 boxing될 것이 자명하다면 처음부터 object를 사용하면 되겠죠.

 

만약 사용하는 value type의 변수가 32비트 이상이고(크기가 크면 클수록 복사될 때 더 큰 비용을 사용합니다)

 

boxing, unboxing이 자주 일어나는 구조라면 struct(value type)가 아니라 class(reference type) 를 사용하는 것이 유리합니다.

 

때문에 객체의 타입을 정할 때는 객체의 크기와 얼마나 자주 copy되는지를 고려해야 할 것입니다.

출처 : http://tit99hds.egloos.com/540695

Posted by Sting!
,