Creative Commons License

Microsoft .NET

닷넷!시작하기
닷넷! Ver 2.0~
닷넷!스킬업
웹개발
윈폼개발
실용모듈개발
Tip & Tech
하루 한 문법

Microsoft .NET 개발자들을 위한 공간입니다. 기초강의에서 부터 고급 기술 정보 및 팁등을 다루도록 하겠습니다.

.

닷넷!스킬업

닷넷 기술을 조금 더 깊이 다루고자 합니다. 특정 주제를 정하지 않고 이슈 발생 시 마다 체계적으로 정리하여 공유하겠습니다. 이전 자료를 옮겨온 곳이기도 합니다.

Boxing-UnBoxing에 따른 재밌는 예제

작성자 : 박종명
최초 작성일 : 2008-05-19 (월요일)
최종 수정일 : 2008-05-19 (월요일)
조회 수 : 4231

www.bullog.net 사이트에 후다닥이라는 분께서 올리신 질문 내용 입니다.
(원본 : http://www.bullog.net/Web/Bulletin/Read.aspx?id=cs_11_qna&item=5&page=0)
 
후다닥님의 깊고 넓은 지식 탐구에 찬사를 보내며.. (전 이런 마인드를 가시진 분을 아주 좋아 합니다 ^^;)
나름대로 정리해 봅니다.
 
아래는 질문 내용 입니다.
=====================================================================
using System;

using System.Collections;

namespace ConsoleApplication1

{

         public class ClassHeader

         {

                  public int x;

                  public int y;

         }

 

         public struct StructHeader

         {

                  public int x;

                  public int y;

         }

 

         public class ClassTest

         {

                  [STAThread]

                  static void Main(string[] args)

                  {

                           ArrayList ach = new ArrayList();

                           ArrayList ash = new ArrayList();

                           ClassHeader ch = new ClassHeader();

                           StructHeader sh = new StructHeader();

 

                           ch.x = 10;

                           ch.y = 20;

                           ach.Add(ch);      // 클래스타입으로 추가

 

                           sh.x = 100;

                           sh.y = 200;

                           ash.Add(sh);      // 구조체타입으로 추가

 

                           // 클래스타입 접근가능. 값이 대입된다.

                           ( (ClassHeader)ach[0] ).x *= 10;

                           ( (ClassHeader)ach[0] ).y *= 10;

 

                           // 구조체타입 접근불가능. 빌드 에러.

                           // 에러메세지 : 대입식의 왼쪽은 변수, 속성 또는 인덱서여야 합니다.

                           ( (StructHeader)ash[0] ).x /= 10;

                           ( (StructHeader)ash[0] ).y /= 10;

 

                  }

         }

}

 

다음 소스를 보시면 하나는 클래스타입이고 또 다른 하나는 구조체타입입니다. 이 두개의 타입을 이용해서 ArrayList에 값을 추가합니다. 추가한 다음 캐스팅하여 각 개체의 멤버에 접근을 시도해봅니다.

 

그러면 클래스타입의 멤버변수에는 값이 제대로 대입되고, 구조체타입은 빌드 에러가 발생합니다.

즉, 값이 대입되지 않는다는 말이죠.

 

굳이 구조체타입의 값을 바꿀려면, 다음과 같이 변수를 하나 선언하여서 ArrayList의 항목을 제거하고 다시 추가하면 됩니다. 다음과 같이 말이죠.

 

StructHeader temp = (StructHeader)ash[0];

temp.x /= 10;

temp.y /= 10;

ash.RemoveAt(0);

ash.Add(ash);

 

그러나 여기서 논점은 왜 클래스로는 접근이 가능하고 구조체는 불가능 할까요?

ValueType과 ReferenceType의 차이점 같은데, 확실한 답을 아시는 분 좀 가르쳐주세요.

 

그리고 또하나 테스트를 해보았습니다. 구조체라도 배열형식으로 접근을 하면 가능하다는 거지요.

아래 예제를 보여드리겠습니다.

 

using System;

using System.Collections;

namespace ConsoleApplication1

{

         public struct StructHeader

         {

                  public int x;

                  public int y;

         }

 

         public class ClassTest

         {

                  [STAThread]

                  static void Main(string[] args)

                  {

                           ArrayList ash = new ArrayList();

                           StructHeader[] sh = new StructHeader[2];

 

                           sh[0].x = 10;

                           sh[0].y = 20;

                           sh[1].x = 100;

                           sh[1].y = 200;

 

                           ash.Add(sh);

                          

                           ( (StructHeader[])ash[0] )[0].x += 5;

                           ( (StructHeader[])ash[0] )[0].y += 5;

                           ( (StructHeader[])ash[0] )[1].x += 50;

                           ( (StructHeader[])ash[0] )[1].y += 50;

                  }

         }

}

 

구체적으로 알고계시는분은 두개의 질문에 답좀 부탁드립니다.

========================================================================================================

 

 

아래는 저의 답변 내용 입니다.

========================================================================================================

 

나름대로 한번 해석해 보겠습니다.
틀린부분이 있다면 조용히(?) 메일을 보내주세용~~
ㅋㅋ .. 농담이구요.. 틀린부분은 과감히 지적바랍니다.

우선 참조타입을 사용할 경우 입니다.

 



1. ArrayList ach = new ArrayList(); 생성
2. ClassHeader ch = new ClassHeader(); 생성
3. ach(ArrayList)에 ch(ClassHeader) 를 추가

최종적으로 ( (ClassHeader) ach[0] ).x  이것은 STACK에 생성되어있는 ch메모리의 주소값을 가지고 HEAP영역의 값에 접근하는 것이다. 예상했던대로 잘 동작한다

------------------------------------------------------------------------------------------------------
다음으로, 값타입을 사용할 경우 입니다.

1. ArrayList ash = new ArrayList(); 생성
2. StructHeader sh = new StructHeader()
;생성
3.4. ash(ArrayList)에 sh(StructHeader) 를 추가

이 3,4과정에서 ValueType인 Struct가 ArrayList(object) 에 add될때 내부적으로 Boxing 이 일어납니다.
(ArrayList는 add의 파라메타로 object를 요구하기에 값타입이 자동으로 참조타입으로 바뀌어서 저장된는 것이지요)

즉, 스택영역에 있는 Struct를 새로운 힙영역으로 복사가 되고 , 실제로 Arraylist 의 첫번째 요소는 복사된 힙영역을 가르키는
주소값을 가지는 셈입니다. 여기까지 ArrayList에 Struct를 저장하면서 Boxing가 내부적으로 일어나느것을 알았습니다

이제 문제는 ( (StructHeader) ash[0] ).x 이것이 속성으로 인식되지 않는다는 것입니다.

ash[0] 즉 ArrayList에서 첫번째 요소를 가르킵니다.
이전에 첫번째 요소에 Struce를 저장했습니다(내부적으로 Boxing가 일어 났구요..)
그러면 이제는 반대로 저장했던 Struct를 빼올때는(?) 내부적으로 UnBoxing가 일어납니다.
즉, Boxing 시 생성된 Heap영역을 값들이 Statck  영역으로 다시 복사가 되는것입니다.

그런데 문제는 위에서 Stack 영역으로 다시 복사가 되는데 그 복사된 메모리 주소를 알수 없습니다. (??? 로 된 부분)
그리하여 속성으로 인식하지 못하는 것입니다.

이것을 런타임이 아닌 컴파일 타임에 잡아낸다는게 조금 이해가 안되지만 다행이기도 합니다.
만일 이것이 런타임에만 잡힌다면..경우에 따라선 아주 심각한 상황이 발생할 수도 이겠지요..


그러면 임의의 StructHeader 타입의 temp변수에 한번 저장하고 난뒤에는 왜 되는지??
이미 다 설명한것이지요..
위의 그림에서 ??? 부분이 temp라는 것으로 바뀌겠지요.. 그래서 제대로 참조를 할 수 있을 테구요..

마지막으로 StructHeader[]를 사용할 경우는 왜 되는지??
StructHeader[] sh = new StructHeader[2]; 로 배열을 생성합니다.
배열은 ValueType가 아니기 때문에 Boxing,UnBoxing가 일어나지 않겠지요..

참고로,
int i = 5;
Console.WriteLine( ( (System.ValueType) i ).ToString() ); <- 잘 됩니다.
int[] iarr = new int[]{1,2};
Console.WriteLine( ( (System.ValueType) iarr ).ToString() ); <- 형식 변환 오류

배열을 사용할 경우의 메모리 구조는 생략 하겠습니다.
-----------------------------------------------------------------------------------------------------


이상 저의 짧은 지식으로 마치 정답인냥(?) 적어봤습니다 ^^
재밌는 질문에 다른 분들의 의견은 어떠신지 궁금합니다.

그럼 수고하세요~~
(
mkex@naver.com)

이름
비밀번호
홈페이지
LC <- 왼쪽의 문자를 오른쪽 박스에 똑같이 입력해 주세요