오진이 블로그

[제프리 리처의 CLR via C#] 박싱된 값 타입과 박싱되지 않은 값 타입 본문

Development/C#

[제프리 리처의 CLR via C#] 박싱된 값 타입과 박싱되지 않은 값 타입

오늘도 진이 2021. 5. 11. 15:41

2부. 타입 설계

5장. 기본, 참조, 값 타입

박싱된 값 타입과 박싱되지 않은 값 타입

  • 값 타입은 참조 타입보다 가벼운데 그 이유는 관리되는 힙에 객채로 할당되지 않기 때문이라고 하였으며, 가비지 컬렉터가 관리하지 않으며 포인터로 지칭되지 않는다. 하지만 값타입에 대한 참조를 얻어야만 하는 일이 자주 있다.
  • 박싱이라는 작업을 통해서 값 타입을 참조 타입으로 변환하는 매커니즘이 있기에 가능하다. 내부적으로, 박싱이 일어나면서 다음과 같은 일이 일어나게 된다.
    • 관리되는 힙에 메모리가 할당된다. 이때 소요되는 메모리의 크기는 값 타입 내에 들어있는 필드들의 메모리 크기에 더하여 관리되는 힙에 할당되는 모든 객체들처럼 타입 객체 포인터와 동기화 블록 인덱스라는 필드를 포함하여 할당된다.
    • 값 타입의 필드들은 새로 할당된 힙 메모리에 복사된다.
    • 객체의 메모리 주소가 반환된다. 이 주소는 이제 객체를 참조하는 것이며 이제 값 타입은 참조 타입으로서 취급된다.
  • FCL에 이제 새로운 버전의 제네릭 컬렉션 클래스가 다수 추가되었으며, 이전에 사용하던 제네릭 타입이 아닌 컬렉션 클래스들은 더 이상 사용하지 않게 되었다. 예를 들어 System.Collections.ArrayList 클래스 대신 System.Collections.Generic.List<T> 클래스를 사용해야 한다. 제네릭 컬렉션 클래스는 이전의 컬렉션 클래스보다 더 뛰어난 기능과 이점을 많이 가져다 준다. 예를 들면 API가 좀 더 간결하게 바뀌고 쓰기 쉬워졌으며, 성능 또한 이전보다 월등히 더 향상된 것을 들 수 있겠다. 하지만 그중에서도 제네릭 컬렉션 클래스가 여러분에게 가져다주는 가장 큰 이점은 제네릭 컬렉션 클래스를 사용하면서 박싱이나 언박싱 절차가 전혀 필요하지 않다는 것이다. 이러한 특징에 따라 관리되는 힙에 불필요하게 만들어지는 객체의 수를 크게 줄일 수 있으며, 결과적으로 여러분이 만드는 응용프로그램에 의한 가비지 수집이 호출되는 횟수 또한 줄일 수 있다. 더 나아가서 컴파일 시점에서의 타입 안정성도 보증할 수 있고, 타입 변환 호출을 하지 않으므로 코드의 가독성을 더 높일 수도 있다.
  • 박싱된 타입 내에 들어있는 필드들의 주소를 가져온다. 이것을 언박싱(Unboxing)이라고 한다. 다음으로, 이 필드들의 값들을 힙 메모리로부터 복사하여 스택 기반의 값 타입 인스턴스 쪽으로 모두 복사해 넣는다.
  • 언박싱이 박싱과 정확히 대칭되는 작업은 아니다.
  • 언박싱 작업은 박싱에 비해 훨씬 적은 연산을 수행한다.
  • 언박싱 자체는 박싱된 객체 안에 들어있는 원래의 값 타입(데이터필드)의 포인터를 얻어오는 것에 불과하다.
  • 박싱된 인스턴스 안에 언박싱된 부분의 포인터를 가져오는 효과가 있다. 그래서 박싱과는 다르게 언박싱 자체는 메모리 복사를 수반하지 않는다.
  • 언박싱 연산이 이루어진 다음 보통은 필드를 복사하는 작업이 이루어진다는 것이다.
  • 박싱과 언박싱/복사 작업으로 인하여 응용프로그램의 속도와 메모리 소비 면에서 모두 성능 저하를 일으키게 되므로, 컴파일러가 편의를 위하여 만들어내는 이러한 코드가 일으키는 부작용에 대해 주의를 기울이면서 이런 작업에 적게 의존하도록 코드를 작성하는 것이 필요하다.
  • 박싱에 대해서 마지막으로 살펴볼 것이 하나 있는데, 여러분이 만든 코드에서 중복되는 하나의 값을 한 번만 박싱하여 여러 번 반복적으로 재사용하도록 신경 써서 만들어주면, 코드가 좀 더 작은 크기를 유지하면서도 더 빠르게 실행될 수 있다는 것이다.
  • 박싱되지 않은 값 타입이 참조 타입보다 왜 가벼운지 상기해보면 다음의 두 가지 이유가 있다고 하였다.
    • 관리되는 힙 메모리가 아닌 스택 메모리 공간에 할당된다.
    • 힙 메모리상에 할당되는 모든 객체들이 지니는 오버헤드가 전혀 없는데, 힙 메모리상에 할당되는 참조 타입 객체들은 기본적으로 타입 객체 포인터와 동기화 블록 인덱스라는 두 개의 추가 필드가 자동으로 할당된다.
  • 인터페이스를 통해서 박싱된 값 타입의 필드를 바꾸는 방법(그리고 그렇게 하면 안 되는 이유)

    중요 이 장의 서두에서 값 타입은 반드시 변경되는 않는 타입으로 만들어야 한다고 했는데, 이 규칙은 정의하는 타입 내의 인스턴스 필드들을 변경하는 어떤 멤버도 정의해서는 안됨을 의미하는 것이다. 사실 필자는 값 타입 내에서 선언되는 필드들은 반드시 readonly로 정의하여 컴파일러가 이러한 필드들에 대해 예기치 않게 변경하는 메서드를 작성하는 것을 사전에 컴파일 시점에서 방어하도록 만들 것을 권했다.

  • 객체의 동일함과 식별
    • System.Object 타입은 Equals 가상 메서드를 제공하여 매개변수로 지정한 객체가 현재 객체와 동일하다고 판단하면 True를 반환하는 동작을 구현하고 있다.
    • Equals 메서드는 반드시 "재귀적"
    • Equals 메서드 호출은 반드시 "대칭성"
    • Equals 메서드 호출은 "전이성"
    • Equals 메서드 호출은 "일관성"
Comments