이 글은 제가 과거에 운영했던 사이트인 http://dotnet.mkexdev.net 의 글을 옮겨온 것입니다. 원본 글은 2010년에 작성되었습니다.
그 전에 운영했었던 사이트(mkex.pe.kr)은 흔적도 없이 사라 졌습니다. 그속의 글들도 모두... 그래서 이 사이트도 사라지기 전에 옮기고 싶은 글을 조금씩 이 블로그로 이동시키려 합니다.
(원본글) http://dotnet.mkexdev.net/Article/Content.aspx?parentCategoryID=1&categoryID=5&ID=671
이 글은 닷넷 기초 지식을 전파하기 위해 2010경에 작성되었으며, 당시 윤성우의 프로그래밍 스터디그룹 네이버 카페에도 필진으로 참여하여 연재했던 글이기도 합니다. 현재 시점에서 조금 달라진 부분이 있을 수 있으나 기본 원리와 언어 기초에 해당하는 부분은 크게 변하지 않았을 것으로 생각하며 이런 부분들을 감안해서 봐 주시기 바랍니다.
“박싱/언박싱은 성능에 좋지 않습니다”
안녕하세요. 박종명입니다. 닷넷 일곱 번째 강좌를 진행하도록 하겠습니다
지난 강좌에서 C#의 자료형에 대해 알아 보았는데요. 자료형을 학습하면서 빼 놓을 수 없는 것이 바로 자료형의 변환일 것입니다. 정수를 실수로 변환하는 등의 내용은 너무 유명(?)하기에 생략하기로 하며 이번 강좌에서는 값 형식과 참조 형식 간 변환이 일어나는 과정에 대해 알아보도록 하겠습니다.
지난 시간에 값 형식(Value Type) 자료는 스택(Stack) 메모리 영역에, 참조 형식(Reference Type) 자료는 힙(Heap) 메모리 영역에 저장된다고 배웠습니다.
이러한 값 형식과 참조 형식간에도 변환이 일어날 수 있는데요.
값 형식을 참조 형식으로 변환하는 것을 박싱(Boxing),
참조 형식을 값 형식으로 변환하는 것을 언박싱(UnBoxing)이라고 합니다
* 박싱(Boxing)
값 형식을 참조 형식으로 변환하는 것을 말하며 스택에 있는 데이터가 힙으로 복사 됩니다.
아래와 같이 값 형식인 정수형 자료 int 를 참조형식인 object 객체로 할당하게 되면 자동으로 박싱이 일어납니다.
박싱은 묵시적으로 일어나며 명시적으로 해도 무방합니다.
int = 123;
object o = i; //박싱(묵시적 변환)
스택에 있는 123이라는 정수형 자료가 힙 영역으로 복사되고 이 영역을 객체 변수 o가 가리키게 됩니다.
값 형식이 참조 형식으로 변환되었는데요. 내부적으로는 값 형식이 System.Object 형식 또는 값 형식이 구현한
인터페이스 형식으로 변환하게 됩니다.
* 언 박싱(UnBoxing)
참조 형식을 값 형식으로 변환하는 것을 말하며 힙에 있는 데이터가 스택으로 복사 됩니다
아래와 같이 박싱된 객체인 o 를 다시 값 형식으로 변환하면 언박싱이 일어납니다.
언박싱은 명시적으로 변환해 줘야 합니다
int i = 123; //값 형식
object o = i; //박싱
int j = (int) o; //언박싱(명시적 변환)
: 언박싱은 박싱한 객체에 대해서만 가능하다
주의할 것은 모든 객체가 값 형식으로 언박싱 될 수는 없습니다.
즉 이전에 값 형식을 박싱하여 생성된 객체에 한해서 언 박싱이 가능합니다.
다시 말해 아래의 코드처럼 박싱이 일어나지 않은 객체에 대한 언박싱 시도는 실패하게 됩니다.
object o = new object();
int j = (int) o; //캐스팅 예외 발생
: 언박싱은 박싱하기 전 형식을 준수해야 한다
또한 박싱 할 때의 값 형식의 타입을 준수해야 합니다.
보통 형 변환 시 short 타입을 더 큰 자료형인 int 타입으로 변환하는 것은 아무런 문제가 없습니다.
그러나 short 타입을 박싱 한 객체를 int 타입으로 언박싱 하는 것은 허용되지 않습니다.
short i = 123; //값 형식
object o = i; //박싱
int j = (int) o; //int로 언박싱 불가능
중간 언어인 IL 코드 열어보면 박싱 언박싱이 일어나는 것을 명확히 알 수 있습니다.
* 박싱/언박싱 그리고 성능
값 형식 <-> 참조 형식 즉 스택과 힙 영역을 자유로이(?) 오가는 박싱/언박싱은 무조건 좋은 걸까요?
그렇지 않습니다. 박싱/언박싱은 비용이 꽤나 많이 드는 작업임을 유념해야 합니다.
값 형식을 박싱 하는 경우 완전히 새로운 객체를 할당하고 구성해야 하며 언박싱에 필요한 캐스팅도
상당한 계산 과정이 필요합니다.
MSDN 에서는 이 과정에 대한 비용을 다음과 같이 설명하고 있습니다.
“boxing 및 unboxing 과정에는 많은 처리 작업이 필요합니다. 값 형식을 boxing할 때는 완전히 새로운 개체가
만들어져야 하며, 이러한 작업에는 할당 작업보다 최대 20배의 시간이 걸립니다. unboxing을 할 때는 캐스팅
과정에 할당 작업보다 4배의 시간이 걸릴 수 있습니다”
* 그럼 언제 그리고 왜 사용하는가?
이처럼 비용이 많이 들고 성능에 악영향을 끼치는 박싱/언박싱은 왜 지원하는 걸까요?
이유야 찾아 보면 여러 가지 있겠습니다만, 대표적으로 사용상의 편의성이라 말하고 싶네요.
C#의 모든 자료형은 System.Object 로부터 상속을 받게 됩니다.
즉 System.Object 로 데이터를 처리할 경우 특정 타입으로 인한 제약사항에서 자유로워 지게 됩니다.
이것은 배열과 같은 복합자료를 다룰 경우 유용한데요.
닷넷 프레임워크에서 제공하는 System.Collections.ArrayList 클래스는 대표적인 복합자료형 입니다.
ArrayList에 저장할 수 있는 요소의 타입은 어떤 형식이라도 상관이 없는데요. 아래 코드를 보면 ArrayList에 정수, 문자, 객체 등을 그 타입을 가리지 않고 마구(?) 추가할 수 있습니다.
System.Collections.ArrayList al = new System.Collections.ArrayList();
al.Add(123); //정수 추가
al.Add("안녕하세요"); //문자열 추가
al.Add(new Program()); //객체 추가
이것은 ArrayListy의 Add 메서드가 object 타입의 매개변수를 취하고 있기 때문입니다.
즉 object 타입이면 모두 가능하며 이 말은 곧 C#의 모든 자료형을 저장할 수 있다는 의미입니다.
이와 같이 특정 자료형에 구애 받지 않고 복합 자료를 다룰 경우 편리성을 제공하게 되는 것입니다.
(배열과 같이 동일한 타입의 자료만 저장할 수 있는 것과는 대조됩니다)
그러나 결국 al.Add(123) 과 같은 명령은 123이라는 정수형 자료가 object 타입으로 박싱이 일어나게 되며 데이터를 가져올 때에 해당 타입으로 언박싱을 해줘야 합니다
object o = al[0];
int j = (int) o;
즉 편리하기는 하지만 그 만큼의 비용은 지불해야 하는 것입니다.
또한 위와 같이 ArrayList에 여러 타입의 자료를 저장하게 되면 값을 가져올 때 박싱 되기 전 자료형으로 명확히 캐스팅을 해줘야 하기 때문에 형식에 대한 불안정성도 증가하게 됩니다.
(참고로 닷넷 2.0 부터는 이러한 ArrayList 의 단점인 고비용과 형식 불안정성을 개선하기 위해 지네릭 버전의 컬렉션을 제공하게 됩니다)
* 자바의 Wrapper 클래스
자바에서는 기본 자료형을 객체처럼 생성하기 위해서 Wrapper 클래스를 사용하게 됩니다.
아래코드는 기본 자료형 int형 정수를 Wrapper클래스인 Integer 클래스를 이용하여 객체로 생성하고 있습니다
Integer i = new Integer(123);
자바에서는 이와 같이 기본 자료형에 대한 Wrapper 클래스를 제공하는데요.
이처럼 기본 데이터 자료형을 Wrapper 객체로 생성하는 것을 박싱이라 하며 Wrapper 객체에서 기본 자료형의 값을 가져오는 것을 언박싱이라 합니다.
다만 닷넷에서는 박싱 시, 자바에서와 같이 기본자료형에 대응하는 Wrapper 클래스가 제공되는 것은 아니지만 그 동작 방식은 매우 유사하며 내부적으로 값 형식을 박싱하면 System.Object 객체로 랩핑 되거나 값 형식이 구현한 인터페이스 형식으로 묵시적 변환이 일어납니다.
따라서 자바의 Auto Boxing과 Auto UnBoxing 에 따른 편의성 제공되지는 않습니다.
다만 개념적 그리고 메모리 구조적인 측면에서는 두 언어에서의 박싱/언박싱은 거의 동일한 개념입니다.
감사합니다. 즐거운 한 주 되세요~~
'SW개발' 카테고리의 다른 글
[C# 기초강좌] 9. C# 배열 (0) | 2023.11.02 |
---|---|
[C# 기초강좌] 8. C# 제어문(조건문과 반복문) (0) | 2023.11.02 |
[C# 기초강좌] 6. C# 자료형 (0) | 2023.11.01 |
[C# 기초강좌] 5. C#응용프로그램의 기본 골격 (0) | 2023.11.01 |
[C# 기초강좌] 4. 명령줄 빌드 (0) | 2023.11.01 |