VisualStudio/C#

[C#] 구조체와 클래스 차이

usingsystem 2022. 8. 10. 12:21
728x90

C# 에서 클래스와 구조체의 차이점에 대해 간단하게 짚어보자.


대부분의 경우 클래스를 사용하는 게 더 익숙할 것이다.

 

우리가 생각하는 객체지향적인 목표에 맞춰 다양한 기능(함수)을 수행해야 하는 경우 클래스를 선언하고 간단하게 자료형(변수) 몇가지를 묶어 하나의 객체로 선언할 때만 구조체를 사용하곤 한다.

 

하지만 엄밀히 말해 구조체에도 함수를 선언해 클래스와 똑같이 사용하는게 가능하며 반대로 클래스를 구조체 처럼 사용해도 문제는 없다.

 

그럼에도 클래스와 구조체는 엄연히 차이점이 존재한다.


1. 클래스는 힙(heap) 영역에 할당 되지만 구조체는 스택(stack) 영역에 할당 된다.

 

힙과 스택의 차이점을 간단하게 설명하자면

 

1. 힙은 런타임에 할당되는 영역이며 new 등의 동적 할당 기능을 통해 프로그램 실행 중 필요한 만큼 가변적으로 확보되어 추후 정리를 위해서는 delete 나 가비지 컬렉션 등의 메모리 관리가 필요한 반면,

 

2. 스택은 함수 내에 포함되어 있는 지역 변수 등이 저장되는 영역으로 미리 그 크기를 파악할 수 있기에 컴파일 타임에 결정되며 함수가 종료 되는 시점에서 자동으로 정리되는 영역이다.

 

클래스 객체는 이 중 힙의 영역에 생성되어 메모리 크기에 좀 더 자유로운 대신 가비지 컬렉션의 영향을 받기에 퍼포먼스에 영향을 줄 수 있다.

 

반면 구조체 객체의 경우는 스택 영역에 생성되어 가비지 컬렉션의 영향을 받지 않기에 성능 면에서는 상대적으로 이점이 있지만 메모리 크기 면에서 한정적이라는 단점이 있다.


2. 클래스는 참조 타입(Reference Type) 이고 구조체는 값 타입(Value Type) 이다.

 

클래스는 참조 타입이기 때문에 객체를 전달했을 때 그 객체는 항상 원본과 동일하다.

 

즉, 클래스로 생성한 객체를 다른 함수에 전달 했을 때 해당 객체의 멤버 변수의 값을 변경시키면 원본의 변수도 변경 되며 전달받은 함수가 종료되더라도 객체는 계속 유효하다.

 

반면 구조체는 값 타입 이므로 함수를 호출해서 객체를 전달하면 원본과 다른 복사본이 생성된다.

 

때문에 이 객체의 멤버 변수를 변경해도 해당 함수의 스택 영역에 복사된 객체의 변수만 변경 될 뿐 원본은 영향을 받지 않는다.

 

그리고 전달 할 때 참조 타입인 클래스는 자신의 메모리 주소 한개만 넘겨주면 되지만 값을 복사해야 하는 구조체는 자신의 크기 만큼의 스택 공간을 할당하게 되므로 크기가 클수록 메모리 사용량도 늘어난다. (스택은 공간 제약이 있으므로 과도하게 커지면 스택 오버플로우가 발생할 수도 있다)

 


3. 구조체는 클래스와 달리 상속이 불가능 하다.

 

이 점은 C++ 과 동일하다.

 

상속이 불가능 하기에 기능의 확장에 제약이 있어 아무래도 복잡한 기능에 적용하기는 쉽지 않다. (단, 인터페이스 상속은 가능하다.)

 

다만, 요즘은 OOP 가 예전처럼 만병통치약이 아니라는 인식이 퍼지고 있고 실제로 상속을 최소화 하는 추세이긴 하다.


이 외에도 접근 지시자의 기본값이 클래스는 private, 구조체는 public 등의 자잘한 몇가지 차이점이 있긴 하지만 가장 큰 차이는 이 두가지 일 것이다.

 

아무래도 클래스 보다는 구조체를 자주 이용하는 게 가비지 컬렉션의 호출 빈도를 줄이고 힙 할당/해제 보다 스택 할당/해제가 훨씬 빠른 특성 등으로 봤을 때 성능상으로 큰 장점을 가지고 있으므로 적극적으로 사용하는 걸 권한다.

 

다만 아무리 스택 할당/해제가 빠르더라도 용량이 커지면 당연히 주소만 전달하는 클래스보다 더 느려지므로 잘 판단해야 한다.

 

그리고 추가로 구조체 내에 클래스를 멤버 변수로 선언할 경우 해당 구조체는 클래스 처럼 힙 영역에 할당 되는 점을 주의해야 한다.


일반적으로 구조체로 사용하기에 적합한 경우는 

  • 객체 내의 멤버 변수가 primitive type(int, float, bool 등) 으로 이루어져 있고
  • 객체의 크기가 작으며
  • 값이 변하지 않고
  • 박싱(boxing)이 자주 일어나지 않는 경우

정도 이다.

 

이 외에는 모두 클래스로 선언해야 한다.

 

정리 하자면 string 같은 클래스를 멤버로 갖지 않는 적은 크기로서 다른 객체에 포함되곤 하는 데이터셋의 경우가 구조체로 선언하기 가장 적합한 케이스이다.

출처: https://skuld2000.tistory.com/16 [아마군의 Dev로그:티스토리]



C#에서 구조체(struct)와 클래스(class)가 어떻게 다른지 알아보자.

 

사용하는데 있어선 차이를 알기 힘들다. 일반적으론 구조체는 함수가 없고 변수들만 저장 가능하고 클래스는 변수이외에 함수도 포함시킬 수 있다고 알고 있다. 하지만 구조체에도 함수를 포함 시킬 수 있다. 그렇다면 무슨차이가 있는지 보도록 하자. 

 

아래 두개의 코드를 보자.

-구조체 코드-

struct Point
{
    public int X;
    public int Y;
    public int Z;
    public Point(int x, int y, int z)
    {
        this.X = x;
        this.Y = y;
        this.Z = z;
    }
    public static Point operator +(Point p1, Point p2)
    {
        return new Point(p1.X + p2.X, p1.Y + p2.Y, p1.Z + p2.Z);
    }
    public String toString()
    {
        return "x: " + X + ", y: " + Y + ", z: " + Z;
    }
}
 

 

-클래스 코드-

class Point
{
    public int X;
    public int Y;
    public int Z;
    public Point(int x, int y, int z)
    {
        this.X = x;
        this.Y = y;
        this.Z = z;
    }
    public static Point operator +(Point p1, Point p2)
    {
        return new Point(p1.X + p2.X, p1.Y + p2.Y, p1.Z + p2.Z);
    }
    public String toString()
    {
        return "x: " + X + ", y: " + Y + ", z: " + Z;
    }
}
 

 

코드상으론 선언을 하는 부분을 제외하면 아무런 차이가 없다.

클래스와 마찬가지로 구조체도 필드로 변수와 함수들을 가질 수 있으며 생성자 역시 가질 수 있다.

 

하지만 큰 차이 2가지가 존재한다.

1. 구조체(struct)는 상속을 할 수 없다.

2. 클래스(class) 객체는 힙(heap)에 할당되지만 구조체(struct) 객체는 스택(stack)에 할당된다.

 

두 가지 차이중 2번이 핵심이다. 

스택의 경우 사용할 수 있는 메모리 크기가 작고 한정적인 반면 힙은 많은 메모리크기를 가질 수 있다.

하지만 스택의 경우 가비지컬랙션에 의해 관리되지 않기 때문에 성능상 많은 장점이 있다.

 

실제 메모리 할당이 어떻게 되는지 아래 실험으로 보자.

우선 Main문 안에 아래와 같이 객체 1000개를 차례로 할당 받는 코드를 작성해보자.

Sleep(100)은 100ms 즉 0.1초간 딜레이는 거는 역할을 한다.

static void Main(string[] args)
{
    Point []p = new Point[1000];
    for (int i=0; i< 1000; i++)
    {
        p[i] = new Point(i, i + 1, i + 2);
        System.Threading.Thread.Sleep(100);
    }
}

 

그리고 디버깅 모드로 실행시킨 후에 메모리 사용량을 보도록 하자. Visual studio 2017에서는 쉽게 메모리 사용양을 확인할 수가 있다. 중간중간 원하는 지점에서 메모리를 표시해줍니다. 개인적인 생각으론 C#이 가비지컬렉션이 완벽하지 않아서인지 메모리이슈가 많아서 해당기능을 강력하게 지원해주는 것 같다.

 

아래 구조체(struct)를 객체로 만들때에는 아래와 같이 힙 영역에 차이가 없는 것을 볼 수 있다. 

 

 

아래 클래스(class)의 경우 시간이 증가함에 따라 객체의 갯수와 힙 메모리의 크기역시 증가하는 것을 볼 수 있다. 

 

 

 

하지만 구조체로 선언을 했어도 객체를 힙영역에 메모리를 할당할 때가 있다. 

 

1. 모든 필드의 합이 16byte를 넘는 경우이다.

2. 구조체안에 클래스 타입을 필드로 가질 경우이다.

 

C#에서는 위의 두경우와 상속이 필요한 경우가 아닌 경우라면 구조체로 선언해서 스택(stack) 메모리 영역을 사용하기를 권장한다. 스택은 기본적으로 가비지 컬렉션의 대상이 아니다. 선언한 함수가 종료되면 그때그때 해제가 되기때문에 heap영역을 사용하는 것보다 좋은점이 많다고 생각된다. 가비지 컬랙션이 자동으로 메모리를 해제시켜주긴 하지만 많은 오버해드가 발생할 수가 있다. 가비지 컬랙션에 동작원리를 알면 이해가 갈 것이다. 

되도록 16바이트가 넘지 않는다면 구조체를 사용하는 버릇을 들이는 것이 좋다.

 

출처 - https://hijuworld.tistory.com/43

728x90