Programing

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

lottogame 2020. 7. 14. 08:18
반응형

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


.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[](더 나은 또는 더 나쁜) 변환 가능 하므로 위의 예에서 s / struct / class / g를 사용하면 문제없이 컴파일되고 실행될 것입니다. 그러나 Baseand Derived가 값 유형이고 배열이 값을 인라인으로 저장하면 문제가 있습니다.

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

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

(Yes, that's abominable assembly, but the point is that we'll increment through the array at known compile-time constants, without any knowledge that a derived type is being used.)

So, if this actually happened, we'd have memory corruption issues. Specifically, within Square(), values[1].A*=2 would actually be modifying values[0].B!

Try to debug THAT!


Imagine structs supported inheritance. Then declaring:

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

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

would mean struct variables don't have fixed size, and that is why we have reference types.

Even better, consider this:

BaseStruct[] baseArray = new BaseStruct[1000];

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

Structs do not use references (unless they are boxed, but you should try to avoid that) thus polymorphism isn't meaningful since there is no indirection via a reference pointer. Objects normally live on the heap and are referenced via reference pointers, but structs are allocated on the stack (unless they are boxed) or are allocated "inside" the memory occupied by a reference type on the heap.


Here's what the docs say:

Structs are particularly useful for small data structures that have value semantics. Complex numbers, points in a coordinate system, or key-value pairs in a dictionary are all good examples of structs. Key to these data structures is that they have few data members, that they do not require use of inheritance or referential identity, and that they can be conveniently implemented using value semantics where assignment copies the value instead of the reference.

Basically, they're supposed to hold simple data and therefore do not have "extra features" such as inheritance. It would probably be technically possible for them to support some limited kind of inheritance (not polymorphism, due to them being on the stack), but I believe it is also a design choice to not support inheritance (as many other things in the .NET languages are.)

On the other hand, I agree with the benefits of inheritance, and I think we all have hit the point where we want our struct to inherit from another, and realize that it's not possible. But at that point, the data structure is probably so advanced that it should be a class anyway.


Class like inheritance is not possible, as a struct is laid directly on the stack. An inheriting struct would be bigger then it parent, but the JIT doesn't know so, and tries to put too much on too less space. Sounds a little unclear, let's write a example:

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

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

If this would be possible, it would crash on the following snippet:

void DoSomething(A arg){};

...

B b;
DoSomething(b);

Space is allocated for the sizeof A, not for the sizeof B.


There is a point I would like to correct. Even though the reason structs cannot be inherited is because they live on the stack is the right one, it is at the same a half correct explanation. Structs, like any other value type can live in the stack. Because it will depend on where the variable is declared they will either live in the stack or in the heap. This will be when they are local variables or instance fields respectively.

In saying that, Cecil Has a Name nailed it correctly.

I would like to emphasize this, value types can live on the stack. This doesn't mean they always do so. Local variables, including method parameters, will. All others will not. Nevertheless, it still remains the reason they can't be inherited. :-)


Structs are allocated on the stack. This means the value semantics are pretty much free, and accessing struct members is very cheap. This doesn't prevent polymorphism.

You could have each struct start with a pointer to its virtual function table. This would be a performance issue (every struct would be at least the size of a pointer), but it's doable. This would allow virtual functions.

What about adding fields?

Well, when you allocate a struct on the stack, you allocate a certain amount of space. The required space is determined at compile time (whether ahead of time or when JITting). If you add fields and then assign to a base type:

struct A
{
    public int Integer1;
}

struct B : A
{
    public int Integer2;
}

A a = new B();

This will overwrite some unknown part of the stack.

The alternative is for the runtime to prevent this by only writing sizeof(A) bytes to any A variable.

What happens if B overrides a method in A and references its Integer2 field? Either the runtime throws a MemberAccessException, or the method instead accesses some random data on the stack. Neither of these is permissible.

It's perfectly safe to have struct inheritance, so long as you don't use structs polymorphically, or so long as you don't add fields when inheriting. But these aren't terribly useful.


This seems like a very frequent question. I feel like adding that value types are stored "in place" where you declare the variable; apart from implementation details, this means that there is no object header that says something about the object, only the variable knows what kind of data resides there.


Structs do support interfaces, so you can do some polymorphic things that way.


IL is a stack-based language, so calling a method with an argument goes something like this:

  1. Push the argument onto the stack
  2. Call the method.

When the method runs, it pops some bytes off the stack to get its argument. It knows exactly how many bytes to pop off because the argument is either a reference type pointer (always 4 bytes on 32-bit) or it is a value type for which the size is always known exactly.

If it is a reference type pointer then the method looks up the object in the heap and gets its type handle, which points to a method table which handles that particular method for that exact type. If it is a value type, then no lookup to a method table is necessary because value types do not support inheritance, so there is only one possible method/type combination.

If value types supported inheritance then there would be extra overhead in that the particular type of the struct would have to placed on the stack as well as its value, which would mean some sort of method table lookup for the particular concrete instance of the type. This would eliminate the speed and efficiency advantages of value types.

참고URL : https://stackoverflow.com/questions/1222935/why-dont-structs-support-inheritance

반응형