NGMsoftware

NGMsoftware
로그인 회원가입
  • 매뉴얼
  • 학습
  • 매뉴얼

    학습


    C# 16. C#의 박싱과 언박싱. (Boxing, Unboxing and Heap, Stack)

    페이지 정보

    본문

    안녕하세요. 엔지엠소프트웨어입니다. 오늘은 C#의 기본중에 기본인 박싱과 언박싱에 대해 알아보겠습니다. 기본이긴 하지만, 상당히 중요한 내용이므로 이 부분은 확실하게 이해하고 넘어가는게 좋겠습니다.

     

    boxing은 값 형식을 object형식 또는 이 값 형식에서 구현된 임의의 인터페이스 형식으로 변환하는 프로세스를 말합니다. CLR(Common Language Runtime)은 값 형식을 boxing할 때 값을 System.Object 내부에 래핑하고 관리되는 힙(Heap)에 저장합니다. unboxing하면 개체에서 값 형식이 추출됩니다. boxing은 암시적이며 unboxing은 명시적입니다. boxing 및 unboxing의 개념은 개체로 처리할 수 있는 모든 값 형식에서 형식 시스템의 C#에 통합된 뷰의 기반이 됩니다.

     

    Memory - Heap, Stack?

    응용 프로그램이 실행되는 동안 저장되는 데이터들은 형식에 따라 메모리에 저장되는 영역이 다릅니다. 아래 이미지는 메모리에 저장되는 영역을 나타내고 있습니다.

    EZXlpDU.png

     

     

    스택(Stack): 메서드내에서 선언된 지역변수로, 블록 내에서만 메모리 할당이 이루어집니다. 이곳에 할당된 값은 블록을 벗어나거나 제어가 종료되면 자동으로 메모리에서 해제됩니다. 이말은 컴파일시 변수들의 크기가 정해지며 그만큼 시스템은 필요한 메모리를 미리 할당할 수 있게 됩니다.

     

    힙(Heap): 동적으로 할당된 메모리로, 힙 영역에 주소값을 가지게 됩니다. 힙에 할당된 메모리는 사용자가 강제로 해제할 수도 있지만, 보통은 가비지 컬렉터(Garbage Collector)가 메모리에서 해제합니다. 컴파일시에 변수의 크기가 정해지지 않으며, 실행중에 메모리가 할당되기 때문에 동적 메모리라고도 합니다.

     

    데이타(Static data): 전역 변수나 정적 변수들이 저장되며 이 영역도 스택과 마찬가지로 시스템에 의해 할당 및 해제되며 컴파일시 크기가 정해지고 할당되는것도 같습니다. 하지만 스택과 다른점은 데이타의 수명인데, 스택 영역은 메서드 또는 블록내에서만 존재하는 반면 데이타 영역은 응용 프로그램이 실행될 때 메모리에 할당되어 이 응용 프로그램이 종료될 때 메모리에서 해제되게 됩니다.

     

    참고로, 메모리에 할당된 값 또는 데이타가 정상적으로 해제(삭제)되지 않으면, 쓰레기 데이타가 메모리에 쌓이게 됩니다. 이런 현상을 메모리 릭(Memory leak)이라고 부릅니다. 전산학에서는 보통 "메모리 누수"라고 표현하기도 합니다.

     

    아래 예제는 boxing과 unboxing에 대해 잘 설명하고 있습니다.

    using System;
    namespace BoxingUnboxing
    {
        class MainApp
        {
            static void Main(string[] args)
            {
                int a = 123;
                // a의 담긴 값을 박싱해서 힙에 저장, 이 때 묵시적 형변환이 일어나기에 명시적으로 형식을 지정할 필요는 없다.            
                object b = a;
                // b에 담긴 값을 언박싱해서 스택에 저장, 이 때는 묵시적 형변환이 일어나지 않는다.             
                // 만약, 명시적으로 형식을 지정하지 않는다면 컴파일 에러가 발생된다.            
                int c = (int)b;
                Console.WriteLine(a);
                Console.WriteLine(b);
                Console.WriteLine(c);
                double x = Math.PI;
                object y = x;           // x에 담긴 값을 박싱해서 힙에 저장             
                double z = (double)y;   // y에 담긴 값을 언박싱해서 스택에 저장                  
                Console.WriteLine(x);
                Console.WriteLine(y);
                Console.WriteLine(z);
            }
        }
    }

     

     

    이 내용이 중요한 이유는 성능과 밀접한 관계가 있기 때문입니다. 단순 할당에서는 boxing과 unboxing을 수행하는 데 많은 계산 과정이 필요합니다. 값 형식을 boxing할 때는 새로운 개체를 할당하고 생성해야 합니다. 정도는 덜하지만 unboxing에 필요한 캐스트에도 상당한 계산 과정이 필요합니다. 제네릭이 아닌 System.Collections에 있는 배열 또는 컬렉션과 같이 많은 수의 boxing이 필요한 경우에는 값 형식을 사용하지 않는게 좋습니다. 아직 배우지는 않았지만, boxing이 일어날 가능성이 있는 작업이라면 제네릭의 형식 지정자를 사용하여 boxing을 방지할 수 있습니다.

     

    boxing 및 unboxing 과정에는 많은 처리 작업이 필요합니다. 값 형식을 boxing할 때는 완전히 새로운 개체가 만들어져야 하며, 이러한 작업은 간단한 참조 할당보다 최대 20배의 시간이 걸립니다. unboxing을 할 때는 캐스팅(형변환) 과정에 할당 작업보다 4배의 시간이 걸릴 수 있습니다.

     

    아래는 boxing 변환이 일어나는 변수를 나타냅니다.

    OsVKGTV.png

     

     

    아래는 unboxing변환이 일어나는 변수를 나타냅니다.

    SxfSI8q.png

     

     

    런타임에 값 형식의 unboxing이 성공하려면 unboxing되는 항목은 이전에 해당 값 형식의 인스턴스를 boxing하여 생성된 개체에 대한 참조여야 합니다. null을 unboxing하려고 하면 NullReferenceException이 발생하고, 호환되지 않는 값 형식에 대한 참조를 unboxing하려고 하면 InvalidCastException이 발생합니다.

     

    .NET 3.0이상 사용한다면 가급적 제네릭을 사용하는게 좋습니다. boxing과 unboxing으로 인해 성능에 오버헤드가 발생되는 것을 방지할 수 있기 때문입니다.

     

    다음 시간에...

    • 네이버 공유하기
    • 페이스북 공유하기
    • 트위터 공유하기
    • 카카오스토리 공유하기
    추천0 비추천0

    댓글목록

    등록된 댓글이 없습니다.