1. Object
C#에 존재하는 모든 타입의 기본 클래스는 Object로 모든 값 타입과 참조 타입이 Object 클래스를 상속받고 있다. Object 클래스에는 필드는 존재하지 않고, Equals(), GetType(), ToString() 등의 메서드들이 존재한다.
2. 참조 타입과 값 타입 (Reference Type & Value Type)
CLR은 참조 타입과 값 타입이라는 두 종류의 타입을 지원한다.
참조 타입은 값을 사용자가 직접 관리하는 힙 영역에 할당하고, 메모리의 주소는 변수명과 함께 스택에 할당한다. 참조 타입을 사용할 때마다 메모리의 할당이 발생하기 때문에 모든 타입이 참조 타입이라면 엄청난 성능 저하가 있을 것이다. 따라서 성능 향상을 목적으로 단순하고 자주 사용되는 타입들을 경량화된 값 타입을 사용하여 힙이 아닌 스레드 스택에 값 자체를 변수명과 함께 할당한다.
메모리 할당 = 어디에 저장되는가
메모리 할당은 인스턴스가 어떤 메모리 영역에 저장되는가의 관점이다.
또한, 이것을 이해하면 Copy by Value, Copy by Reference의 차이에 대해서도 쉽게 이해할 수 있다.
※ CLR(Common Language Ructime) : .NET은 코드를 실행하고 개발 프로세스를 더 쉽게 만드는 서비스를 제공하기 위해 공용 언어 런타임이라는 런타임 환경을 제공합니다.
※ 스택과 힙의 차이점 : https://cobook.tistory.com/97
3. 박싱과 언박싱 (Boxing & Unboxing)
박싱이라는 용어는 스택의 값을 힙에 저장하기 위해 객체로 포장한다는 뜻으로, 값 타입을 참조 타입으로 변환하여 값을 포함하는 객체를 힙에 생성하는 것이다. 언박싱은 박싱된 참조 타입으로부터 원래의 값을 다시 추출하는 연산이다.
int i = 1234;
object box = i; // 박싱
int k = (int)box; // 언박싱
박싱은 힙에 참조 타입을 위한 메모리를 할당하고 값을 복사하는 복잡한 과정을 거치므로 캐스트 연산자보다 훨씬 더 느리고 비효율적이다. 컴파일러가 필요할 때 내부적으로 박싱을 하므로 사실 개발자가 직접 박싱을 할 필요는 거의 없다. 박싱은 참조 타입을 받는 메서드에게 값 타입을 넘기고자 할 때나 또는 그 반대의 경우에 두 형식 간을 변환하기 위해 주로 사용된다. 직관적인 예는 다음 코드이다.
string s = 1234.ToString();
정수 상수 1234를 문자열 변수에 대입하기 위해 ToString 메서드를 호출하여 문자열로 변환했다. ToString은 클래스 소속이기 때문에 이 메서드를 호출하기 위해서는 클래스 타입의 객체가 필요하다. 그런데 1234라는 상수는 어디까지나 값(Literal)일 뿐이며 클래스로부터 만들어진 객체가 아니므로 메서드를 호출할 수 없다. 이때 컴파일러는 1234를 object 타입으로 박싱한 후 ToString 메서드를 호출한다.
4. 비교
값 타입 (Value Type)
- 스택에 할당되며 변수가 값 자체를 직접 가진다.
- 정수, 실수, 문자, 논리 등의 내장 타입과 열거형, 구조체가 값 타입이며 주로 크기가 작고 길이가 고정적인 값들을 저장한다.
- 힙에 할당되는 인스턴스와 다르게 할당 시 모든 값의 복사가 일어나므로 매개변수 등으로 쓰이면 느리다.
- 값 타입의 변수를 다른 값 타입의 변수로 대입하면 필드 단위로 하나씩 복제가 이뤄진다.
- 박싱되지 않은 상태로 할당되면 메서드와 함께 선언되고 할당되기 때문에 메서드의 실행이 끝나자마자 메모리가 할당 해지된다.
- 동기화 블록 인덱스가 없기 때문에 다중 스레드 환경에서 인스턴스에 대한 접근 제어를 위해 쓰는 Threading.Monitor나 C#의 lock 구문을 쓸 수 없다.
참조 타입 (Reference Type)
- 값 자체를 가지는 것이 아니라 값이 저장된 위치(인스턴스 주소)만을 가진다. 값 자체는 힙에 할당한다.
- 문자열, 클래스, 배열 등 길이가 가변적인 것들이 속한다.
- 선언 후 바로 사용할 수 없고 힙에 기억 장소를 할당하는 초기화를 거쳐야 한다.
- 참조 타입의 변수끼리 대입이 발생하면 단순히 메모리 주소만 복제된다.
- GC(Garbage Collector)를 만날 때까지 할당이 해지되지 않는다.
- 힙 메모리 상에 할당되는 참조 타입 객체들은 기본적으로 타입 객체 포인터와 동기화 블록 인덱스라는 두 개의 추가 필드가 자동으로 할당되기 때문에 값 타입보다 무겁다.
※ 인스턴스 주소 : 실제 인스턴스가 할당된 메모리의 주소
※ 타입 객체 포인터 (Type Object Pointer - TOP (a.k.a Method Table Pointer)) : 타입에 대한 정보
※ 동기화 블록 인덱스 (Sync Block Index - SBI (a.k.a Object Header Word)) : 스레드 동기화에 사용된다. lock 키워드를 사용하여 하나의 스레드만 일할 수 있도록 임계 영역(critical section)을 지정한다.
※ 동기화 (Synchronization): 여러 스레드가 한 리소스를 사용하려 할 때 사용하려는 스레드 하나를 제외한 나머지 스레드들은 리소스를 사용하지 못하도록 막는 것을 말한다.
※ 스레드 (Thread) : 프로세스 내에서 실제로 작업을 수행하는 주체
※ 프로세스 (Process) : 컴퓨터에서 연속적으로 실행되고 있는 컴퓨터 프로그램
※ 리소스 (Resource) : 컴퓨터 시스템 내의 실질적 또는 가상의 부품이나 요소
'C#' 카테고리의 다른 글
[C#] 문자열 String & StringBuilder (0) | 2023.03.31 |
---|---|
[C#] 상수 const & readonly (0) | 2023.03.30 |
[C#] 네이밍 룰 (0) | 2023.03.28 |
[C#] 지역 변수, 전역 변수 (1) | 2023.03.28 |
[C#] 1. C# 기초 (0) | 2022.11.17 |