언어/C#

C# 의 타입 - 1. 타입의 기초

tsyang 2020. 10. 31. 18:21

System.Object


c#의 모든 타입들은 System.Object를 상속받는다.

 

class A  // class A : System.Object 와 같다. (암시적으로 상속)
{
    // code
}

 

Object 클래스에는 필드는 존재하지 않으며 다음과 같은 메서드들이 존재한다.

(~Object()는 Finalize 메서드이며 protected임)

 

namespace System
{
    public class Object
    {
        public Object();
        ~Object();
        public static bool Equals(Object objA, Object objB);
        public static bool ReferenceEquals(Object objA, Object objB);
        public virtual bool Equals(Object obj);
        public virtual int GetHashCode();
        public Type GetType();
        public virtual string ToString();
        protected Object MemberwiseClone();
    }
}

 

 

New 연산자


CLR의 모든 객체들은 반드시 new 연산자에 의하여 만들도록 하고 있다.

 

다음과 같은 코드가 있다. 

Employee e = new Employee("ConstructorParam1")

이때 new 연산자는 무슨 일을 할까?

 

  1. 인스턴스의 필드들을 메모리에 할당하기 위한 바이트를 계산한다.
  2. 힙 상의 모든 객체에는 별도로 타입 객체 포인터(Type Object Pointer)와 동기화 블록 인덱스(Sync Block Index)를 추가한다. 이 추가 멤버들은 객체의 실제 크기에 포함된다. (value type에는 생성되지 않는다.)
  3. 계산한 바이트수만큼 메모리를 할당하며, 모든 바이트를 0으로 초기화한다.
  4. 마찬가지로 타입 객체 포인터와 동기화 블록 인덱스 멤버를 초기화한다.
  5. 클래스 타입의 생성자와 파라미터가 전달된다. 생성자는 상속 관계에 따라 거슬러 올라가며 종국에는 System.Object의 생성자를 호출한다. (System.Object의 생성자는 아무 일도 하지 않고 반환한다.)

 

2번과 관련해서 뒤이은 글에 설명하겠지만 참조 타입 객체들의 경우 타입 객체 포인터와 동기화 블록 인덱스가 추가되기에  value type의 객체보다 더 많은 메모리를 쓰게 된다.

 

3번과 관련해서 구조체의 경우 new 연산자를 사용하지 않고도 인스턴스를 생성할 수 있는데, 이 경우 초기화를 따로 해주어야 프로그램이 잘 동작한다.

    struct MyStruct
    {
        public int val;
    }
    class Program
    {
        static void Main(string[] args)
        {
            MyStruct A;
            MyStruct B = new MyStruct();

            int a, b;
            a = A.val; // 오류 : 할당되지 않은 필드 val을 사용하고 있습니다.
            b = B.val;
        }
    }

 

 

 

실행 시점에서 타입과 메모리


 

class Employee

- public Int32 GetYearsEmployed() {...}

- public virtual String GetProgressReport() {...}

- public static Employee Lookup(String name) {...}

 

class Manager : Employee

- public override String GetProgressReport() {...}

 

void M
{
    Employee e;
    e = new Manager();
    e = Employee.Lookup("Joe");
    year = e.GetYearsEmployed();
    e.GetProgressReport();
}

위와 같은 구조의 클래스들이 있고 M이라는 메서드가 있다. 이 때 M이라는 메서드를 실행하면 메모리는 다음과 같을 것이다.

 

  • e는 new연산자를 통해 새로운 Manager 객체를 힙에 할당하지만 곧이어 Employee.Lookup("Joe")를 통해 새로운 객체를 가리키게 된다. (기존에 있던 객체는 GC의 수거 대상이 된다.)
  • 모든 힙의 객체에는 타입 객체 포인터와 동기화 블록 인덱스가 있다.
  • 타입 객체 포인터는 마찬가지로 힙에 할당된 타입 객체 클래스를 가리키게 된다.
  • 타입 객체 클래스에도 타입 객체 포인터가 있다. 이들은 "Type"이라는 이름의 모든 타입의 조상 객체를 가리키게 된다. "Type" 타입 객체의 포인터는 자기 자신을 가리킨다.