[.net] 구조체가 상속을 지원하지 않는 이유는 무엇입니까?

.NET의 구조체는 상속을 지원하지 않지만 이러한 방식으로 제한되는 이유 는 명확하지 않습니다 .

구조체가 다른 구조체에서 상속되는 것을 막는 기술적 이유는 무엇입니까?



답변

값 유형이 상속을 지원할 수없는 이유는 배열 때문입니다.

문제는 성능 및 GC 이유로 인해 값 유형의 배열이 “인라인”으로 저장된다는 것입니다. 예를 들어, 소정의 new FooType[10] {...}경우, FooType참조 형식 11 개체는 관리 힙 (각 유형의 인스턴스의 배열 한, 10)에 생성한다. 경우 FooType값 대신 타입 인스턴스 만이 관리 힙에 생성 될 것이다 – 배열 자체 (각 배열의 값으로서 배열 “인라인”를 저장한다).

이제 값 유형이있는 상속이 있다고 가정합니다. 위의 배열의 “인라인 스토리지”동작과 결합하면 C ++에서 볼 수 있듯이 나쁜 일이 발생 합니다.

이 의사 C # 코드를 고려하십시오.

struct Base
{
    public int A;
}

struct Derived : Base
{
    public int B;
}

void Square(Base[] values)
{
  for (int i = 0; i < values.Length; ++i)
      values [i].A *= 2;
}

Derived[] v = new Derived[2];
Square (v);

정상적인 변환 규칙에 따라 a Derived[]Base[](더 좋든 나쁘 든) a 로 변환 할 수 있으므로 위의 예에서 s / struct / class / g하면 문제없이 예상대로 컴파일되고 실행됩니다. 그러나 경우 BaseDerived값 종류가 있으며, 배열은 우리가 문제가, 인라인 값을 저장합니다.

Square()대해 아무것도 모르기 때문에 문제 Derived가 있습니다. 배열의 각 요소에 액세스하기 위해 포인터 산술 만 사용하여 일정한 양 ( sizeof(A)) 만큼 증가합니다 . 어셈블리는 모호하게 다음과 같습니다.

for (int i = 0; i < values.Length; ++i)
{
    A* value = (A*) (((char*) values) + i * sizeof(A));
    value->A *= 2;
}

(예, 그것은 끔찍한 어셈블리이지만, 요점은 파생 된 형식이 사용되고 있다는 사실을 알지 못해도 알려진 컴파일 타임 상수에서 배열을 통해 증가한다는 것입니다.)

따라서 이것이 실제로 발생하면 메모리 손상 문제가 발생합니다. 특히, 내 Square(), values[1].A*=2것입니다 실제로 수정 될 values[0].B!

디버깅하려고 THAT !


답변

구조체가 상속을 지원한다고 상상해보십시오. 다음 선언 :

BaseStruct a;
InheritedStruct b; //inherits from BaseStruct, added fields, etc.

a = b; //?? expand size during assignment?

구조체 변수의 크기가 고정되어 있지 않다는 것을 의미하므로 참조 유형이 있습니다.

더 좋은 점은 다음과 같습니다.

BaseStruct[] baseArray = new BaseStruct[1000];

baseArray[500] = new InheritedStruct(); //?? morph/resize the array?


답변

구조체는 참조를 사용하지 않습니다 (박스형이 아니면 피해야합니다). 따라서 참조 포인터를 통한 간접 지정이 없기 때문에 다형성은 의미가 없습니다. 객체는 일반적으로 힙에 있으며 참조 포인터를 통해 참조되지만 구조체는 스택에 할당되거나 (박스형이 아닌 경우) 힙의 참조 유형이 차지하는 메모리 “내부”에 할당됩니다.


답변

문서 의 내용 다음과 같습니다 .

구조체는 값 의미가있는 작은 데이터 구조에 특히 유용합니다. 복소수, 좌표계의 점 또는 사전의 키-값 쌍은 모두 구조체의 좋은 예입니다. 이러한 데이터 구조의 핵심은 데이터 멤버가 적고 상속 또는 참조 ID를 사용할 필요가 없으며 할당이 참조 대신 값을 복사하는 값 의미 체계를 사용하여 편리하게 구현할 수 있다는 것입니다.

기본적으로 간단한 데이터를 보유해야하므로 상속과 같은 “추가 기능”이 없습니다. 기술적으로는 제한된 종류의 상속 (다형성이 아니라 스택에 있기 때문에)을 지원하는 것이 기술적으로 가능할 수 있지만 상속을 지원하지 않는 것이 설계 선택이라고 생각합니다 (.NET의 다른 많은 것들처럼) 언어가 있습니다.)

다른 한편으로 나는 상속의 이점에 동의하고 우리 모두가 struct다른 사람으로부터 물려 받기를 원하는 지점에 도달했고 그것이 불가능하다는 것을 깨달았다 고 생각합니다. 그러나 그 시점에서 데이터 구조는 아마도 너무 발전하여 어쨌든 클래스가되어야합니다.


답변

구조체가 스택에 직접 배치되기 때문에 상속과 같은 클래스는 불가능합니다. 상속하는 구조체는 부모보다 더 클 것이지만 JIT는이를 모르고 너무 적은 공간에 너무 많은 것을 넣으려고합니다. 약간 불분명하게 들리면 예제를 작성해 보겠습니다.

struct A {
    int property;
} // sizeof A == sizeof int

struct B : A {
    int childproperty;
} // sizeof B == sizeof int * 2

이것이 가능하면 다음 스 니펫에서 충돌이 발생합니다.

void DoSomething(A arg){};

...

B b;
DoSomething(b);

공간은 B의 크기가 아니라 A의 크기에 할당됩니다.


답변

수정하고 싶은 점이 있습니다. 구조체가 상속 될 수없는 이유는 스택에있는 구조가 올바른 것이기 때문이지만, 동일한 설명은 절반이 정확합니다. 다른 값 유형과 마찬가지로 구조체 는 스택에 있을 수 있습니다 . 변수가 선언 된 위치에 따라 다르기 때문에 스택 또는 힙에 위치 합니다. 이것은 그들이 각각 지역 변수 또는 인스턴스 필드 일 때입니다.

그렇게 말하면서 Cecil은 이름이 있습니다.

저는 이것을 강조하고 싶습니다. 값 유형 스택에 존재할 수 있습니다 . 그렇다고 항상 그렇게한다는 의미는 아닙니다. 메소드 매개 변수를 포함한 지역 변수가 사용됩니다. 다른 모든 것은 그렇지 않습니다. 그럼에도 불구하고 상속 될 수없는 이유는 여전히 남아 있습니다. 🙂


답변

구조체는 스택에 할당됩니다. 이것은 값 의미 체계가 거의 무료이며 구조체 멤버에 액세스하는 것이 매우 저렴하다는 것을 의미합니다. 이것은 다형성을 방지하지 않습니다.

가상 함수 테이블에 대한 포인터로 각 구조체를 시작할 수 있습니다. 이것은 성능 문제가 될 수 있지만 (모든 구조체는 적어도 포인터의 크기가 됨) 가능합니다. 이것은 가상 기능을 허용합니다.

필드 추가는 어떻습니까?

글쎄, 스택에 구조체를 할당 할 때 특정 공간을 할당합니다. 필요한 공간은 컴파일 시간에 결정됩니다 (사전 또는 JIT 팅시). 필드를 추가 한 다음 기본 유형에 할당하는 경우 :

struct A
{
    public int Integer1;
}

struct B : A
{
    public int Integer2;
}

A a = new B();

스택의 알려지지 않은 부분을 덮어 씁니다.

대안은 런타임이 A 변수에 sizeof (A) 바이트 만 기록하여이를 방지하는 것입니다.

B가 A의 메서드를 재정의하고 Integer2 필드를 참조하면 어떻게됩니까? 런타임에서 MemberAccessException이 발생하거나 메서드가 대신 스택의 일부 임의 데이터에 액세스합니다. 둘 다 허용되지 않습니다.

구조체를 다형성으로 사용하지 않거나 상속 할 때 필드를 추가하지 않는 한 구조체 상속을 사용하는 것이 완벽하게 안전합니다. 그러나 이것들은별로 유용하지 않습니다.