C++에서는 struct 와 class 의 차이가 멤버들의 기본 접근 제어 지시자(private, public, protect)를 제외하고는 없습니다.

 

다만 c#에는 struct이냐? 혹은 class이냐? 에 따라서 값 타입과 참조타입의 개념의 차이가 존재합니다.

이미 존재하는 인스턴스를 참조하지 않는이상 참조타입으로 선언된 변수를 사용하기 위해선 반드시 할당의 과정을 거쳐야합니다.

 

ClassTest obj = new ClassTest(10,20,30);

 

일반적으로 이렇게 new 를 통해 운영체제에 의해 공간을 할당받아 생성되는 인스턴스는 힙(Heap)영역에 올라가게 됩니다.

 

반대로 struct 타입으로 선언된 변수는 일반 변수처럼 선언하되, 멤버변수들을 초기화 하기만 하면 new 를 통한 할당없이도 사용할 수 있습니다.

 

다만 struct를 쓰기에는 c++에 비해서는 의외로 까다로운 점이 있습니다. 기본 생성자를 사용할 수 없으며 선언 후 이를 사용할려면 멤버를 모두 초기화 해야하며 클래스로 부터의 상속을 사용할 수 없으며(인터페이스는 가능) 불변성을 띄는경우가 있다 등등의 몇가지 제약사항이 있습니다.

 

//아래와 같이 사용할 경우 멤버를 모두 초기화해주기 전에는 사용할 수 없습니다. 사용측면에선 귀찮고 까다롭습니다.

StructTest obj;

obj.mem1 = 10;

obj.mem2 = 20;

obj.mem3 = 30;

obj.Test();

 

 

따라서 이점이 까다롭다는 이유로 일반적으로 struct 에도 new로 생성자를 호출해서 사용하시는 분들도 많이 보았습니다. 

 

//3가지 멤버를 한번에 초기화 해주는 생성자를 호출해버립니다. 편해졌습니다.

StructTest obj2 = new StructTest(10,20,30);

 

당연히 c++의 개념대로 라면 new를 하지않은 StructTestobj는 스택(Stack)영역에 있어야 할 것이고 new를 한 obj2는

힙영역에 있어야 할 것입니다.

 

그리고 아래의 코드는 동적할당을 했기때문에 올라가는 영역에 대해서는 별 다른 차이점이 없다고 생각할 수 있습니다.

 

//Class 든 Struct든 모두 new를 했기때문에 Heap영역에 올라갈 것이다?

ClassTest obj = new ClassTest(10,20,30);

StructTest obj2 = new StructTest(10,20,30);

 

 

다만 실상은 그렇지 않습니다.

MS공식 레퍼런스에서 언급하듯 new키워드를 사용했더라도 Struct타입의 데이터는 일반적으로 Stack영역에 할당됩니다.

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
namespace CSharpTest
{
    
    struct StructTest
    {
        public int mem1;
        public int mem2;
        public int mem3;
 
        public StructTest(int m1, int m2, int m3)
        {
            mem1 = m1;
            mem2 = m2;
            mem3 = m3;
        }
    }
    class ClassTest
    {
        int mem1;
        StructTest structMem; //Stack? Heap?
    }
 
    class Program
    {
 
        static void Main(string[] args)
        {
            ClassTest test = new ClassTest();
        }
 
    }
 
}
cs

 

그리고 위의 코드를 IL (중간언어)로 디스어셈블 한 결과는 다음과 같습니다.

 

 

분명히 new를 사용하였는데도 불구하고 Struct 타입에 대해서는 단순히 생성자만 call하는 것을 볼 수 있으며 

똑같이 new를 사용한 Class타입은 newobj (관리되는 힙 영역 할당 후 생성자 call) 하는 것을 볼 수 있습니다.

 

다만 C#이 익숙하지 않으신 분들의 경우 Struct타입은 무조건 스택(Stack)영역에 올라간다는 생각만 하여선 안됩니다.

다음의 경우가 예외적인 사항입니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
namespace CSharpTest
{
    
    struct StructTest
    {
        public int mem1;
        public int mem2;
        public int mem3;
 
        public StructTest(int m1, int m2, int m3)
        {
            mem1 = m1;
            mem2 = m2;
            mem3 = m3;
        }
    }
    class ClassTest
    {
        int mem1;
        StructTest structMem; //Stack? Heap?
    }
 
    class Program
    {
 
        static void Main(string[] args)
        {
            ClassTest test = new ClassTest();
        }
 
    }
 
}
cs

 

 

 

 

ClassTest 클래스의 StructTest 타입의 변수 structMem은 현재 ClassTest타입의 멤버변수(필드) 로써 포함되어 있습니다.

 

ClassTest 객체를 하나 생성하였으며 이 인스턴스는 현재 힙 영역에 존재할 것입니다.

따라서 StructTest 타입의 멤버인 structMem 역시 해당 인스턴스가 존재하는 곳에 동일하게 올라가 있습니다.

(마치 지역성을 띄고 있습니다. 저 structMem은 자신의 지역(?)인 test 오브젝트가 소멸할 때 같이 소멸될 것입니다.)

 

 

 

따라서 이 개념을 혹여나 모르시고 계셨던 분들의 경우 위와 같은 혼동을 하지않도록 주의 하여야 합니다.

 

 

 

 

 

 

P.S 

 

 

여담으로 필자의 경우 C++로 개발 할때는 둘의 차이점은 별로 없었기에 일반적으로 Struct는 단순히 데이터 덩어리 집합 (패킷 프로토콜 정의, 객체 표현이 필요없는 데이터 덩어리 모음용 등등)으로 사용하고 이외에 객체 자체를 표현해야 하는경우는 Class를 사용하곤 했습니다. 

 

Struct의 사용은 일반적으로는 힙 메모리의 동적 할당을 하지 않기때문에 속도면에서 빠르다고 볼 수 있습니다.

다만 대입이나 값의 전달과 관련된 부분에 대해서는 다시 생각해볼 필요가 있습니다.

 

C#에서 Class 타입은 경우 참조 타입(Reference Type) 이기때문에 서로 단순 대입을 할때는 레퍼런스 값만 복사(참조 복사)가 일어납니다.  이는 내부적으로 포인터로 구현되어 있을 것이며 4바이트 값의 복사만 일어납니다. (한번에 복사가 가능합니다.)  

 

다만 값 타입(Value Type)인 Struct 타입의 경우 일반적으로 서로 단순 대입을 할때는 메모리 값 자체의 복사가 일어납니다. 이것이 한번에 일어나면 좋겠으나 한번 복사할 수 있는 양은 정해져 있습니다. 따라서 일정 바이트크기를 넘어간 데이터의 메모리 값 복사가 일어날때는 반복적으로 복사가 일어나기 때문에 여기에 시간이 소모될 수 있습니다.

(하필 이런 값타입끼리의 단순 대입이나 값전달 코드들은 개발하다 보면 매 루프마다 호출된다던지 등등 굉장히 흔하게 호출되곤 합니다.)

 

.Net에서는 이전버전에서는 16바이트 현 버전에서는 약 24~26바이트 정도 까지 한번에 복사가 가능 하므로 구조체의 크기가 이보다 훨씬 더 커지는 경우 메모리 값 복사에 다소 신경 쓸 필요가 있습니다. 

C++의 경우 이런 거대한 데이터를 전달해야할 상황일때 포인터로 전달하는 방법이 있겠으나 C#은 일반적으론 거대한 Struct를 전달 하는 경우에 대한 마땅한 해결책이 없습니다. 따라서 가급적 Struct는 크지않은 데이터에 대해서 사용하는 편이 좋습니다.

  1. joshow 2021.05.18 19:32

    좋은 글 감사합니다 ^^

  2. yunslee 2021.06.21 23:02

    감사합니다~

+ Recent posts