2016년 5월 21일 토요일

박싱 언박싱


   박싱
   참조형자료형인 object 에 struct 타입의 값을 대입했을때
   참조형인 object 는 struct 에 참조되는 값을 직접받을 수 없어
   struct 타입과 같은 인스턴스를 힙에 할당하고
   그 힙에 할당된 인스턴스를 참조한다.
   이는 참조가 끊기면 가비지가 된다.

먼저 메모리 구조를 좀 알아야 한다.

프로그램이 실행되기 위해서는 그 프로그램 내에서 사용되는 변수들을 위한 메모리가 할당되어야 한다.
프로그램이 실행되면 CPU는 그 프로그램을 위한 메모리를 할당한다.
메모리는 데이터의 성향에 따라 대개 4가지 영역으로 나뉘어져 프로그램에서 읽어드린 데이터들을 분류합니다.

그 4가지 영역은 코드 영역, 데이터 영역, 힙 영역, 스택 영역으로 나뉩니다.

1) 코드 영역

프로그램이 실행되면 모든 코드들이 이 코드 영역에 저장됩니다. 그리고 CPU가 와서 하나씩 차례로 실행합니다.

2) 데이터 영역

전역변수와 static 변수들이 저장됩니다. 즉, 이곳의 데이터들은 프로그램이 시작될 때 생겨나서 프로그램이 끝날때 까지 남아 있습니다.

3) 힙 영역

메모리 동적 할당 시 사용하는 공간입니다.

4) 스택 영역

지역 변수와 매개 변수가 할당됩니다.



int와 같은 자료형은 생성과 동시에 4byte라는 메모리가 할당되어야 한다.

하지만, string과 같은 자료형은 할당과 동시에 몇 바이트를 할당해야 하는지 알 수 없다.

따라서 int형으로 선언된 변수는 스택에 저장되고

string형으로 선언된 변수는 힙에 저장되는 것이다.

정확히 따지면,

int,byte,char,single,double,boolean,decimal과 같은 자료형들이 Value Type이고

String, Array, Object, Class, Interface, Delegate과 같은 자료형들이 Reference Type이라고 한다.


실제 메모리가 사용되는 과정을 보면서 더 잘 이해해보자.

예를 들어,

int a = 1;

이렇게 선언했다고 하자.
이렇게 된다는 말이다.

int형 변수에는 1과 같은 값이 저장된다.

즉, Value Type 변수에는 Value가 저장된다.


그렇다면,

string b = "bbbb";

이렇게 선언했다면?


이렇게 실제 값이 저장되어 있는 메모리의

주소를 가지고 있는 포인터를 가집니다.

실제 값을 가지고 있는 메모리의

참조를 가진다는 말입니다.

즉, Reference Type 변수는 Reference를 가집니다.






이런걸 왜 설명했을까요?

박싱(Boxing)이 바로 Value Type 변수를 Reference Type 변수로 바꾸는 과정이고

언박싱(UnBoxing)은 Reference Type 변수를 Value Type 변수로 바꾸는 과정이기 때문이다.


더 자세히 알아보도록 하자.

1. 박싱 (Boxing)


박싱은 힙에 Value Type을 저장하는 데 사용됩니다. 박싱은 Value Type을 object형식(Reference Type) 또는 이 Value Type에서 구현된 임의의 인터페이스 형식으로 변환하는 암시적 변환입니다. Value Type을 박싱하면 힙에 오브젝트 인스턴스가 할당되고 값이 새 오브젝트에 복사됩니다.

int i = 123;

//박싱 연산한다.
object o = i;

이 코드의 결과로 힙에 있는 int 형식의 값을 참조하는 o 오브젝트가 스택에 생성됩니다. 이 값은 변수i에 할당된 Value Type 값의 복사본입니다. 다음 그림에서는 두 변수 io의 차이점을 보여 줍니다.

다음 예제에서와 같이 명시적으로 boxing을 수행할 수도 있지만 명시적 boxing이 반드시 필요한 것은 아닙니다.

(궁금한 점 : O는 Reference 타입이므로 힙 영역에 저장되어야 하는것 아닌가??)
int i = 123;

object o = (object)i;

근데 만약 i값을 바꿔준다면 어떻게 될까?
o에 저장된것은 힙 영역에 있는 오브젝트의 참조이다.

이 오브젝트는 i가 저장하고 있는 값의 복사본을 저장하고 있다.

즉, i와 직접적으로 연결되어 있는 것이 아니라, 단지 i의 값을 복사해서 가지고 있을 뿐이므로

i값에는 영향을 미치지 않는다.


2. 언박싱(UnBoxing)


unboxing은 object 형식(Reference 형식으로 봐도 무방할듯)에서 값 형식으로, 또는 인터페이스 형식에서 해당 인터페이스를 구현하는 값 형식으로의 명시적 변환입니다. unboxing 연산 과정은 다음과 같습니다.
  • 오브젝트 인스턴스가 지정한 Value Type을 boxing한 값인지 확인합니다.
  • 인스턴스의 값을 Value Type 변수에 복사합니다.
다음 문은 boxing 및 unboxing 연산을 모두 보여 줍니다.

int i =123;

object  o = i;   // 박싱

int j = (int)o;  // 언박싱

런타임(프로그램이 실행되고 있는 동안)에 특정 Value Type의 언박싱이 성공하려면 언박싱되는 항목(이 경우에는 o)은 이전에 해당 Value Type의 인스턴스를 boxing하여 생성된 개체의 주소값을 가리키는 참조여야 합니다.
null(어떠한 주소값도 없을 경우)을 unboxing하려고 하면 NullReferenceException이 발생합니다.
호환되지 않는 값 형식에 대한 참조를 unboxing하려고 하면 InvalidCastException이 발생합니다
(위 예에서 j의 자료형이 float이거나 double이면 InvalidCastException이 발생한다는 말이다.)

예제

다음 예제에서는 잘못된 unboxing의 경우와 그 결과로 발생하는 InvalidCastException을 보여 줍니다.
이 예제에서는 trycatch를 사용하여 오류가 발생할 때 오류 메시지를 표시합니다.

이 프로그램의 출력은

Specified cast is not valid. Error: Incorrect unboxing.

다음 코드를

int j = (short)o;

다음 코드로 바꿔야한다.

int j = (int)o;

3. 왜 박싱과 언박싱을 할까?


4. 정리

댓글 없음:

댓글 쓰기