Is multiple-level “struct inheritance” guaranteed to work everywhere?

社会主义新天地 提交于 2020-05-15 06:20:46

问题


I know that in C, the first member of a struct is guaranteed to have no padding before it. Thus &mystruct == &mystruct.firstmember is always true.

This allows the "struct inheritance" technique, as described in this question:

typedef struct
{
    // base members

} Base;

typedef struct
{
    Base base;

    // derived members

} Derived;

// ... later
Base* object = (Base*) malloc(sizeof()); // This is legal

However, I'd like to make sure that this actually works safely with unlimited layers of "inheritance". E.g.:

typedef struct
{
    // members

} A;

typedef struct
{
    A base;

    // members

} B;

typedef struct 
{
    B base;

    // members
} C;

Are all of the following uses guaranteed to work?

A* a = (A*) malloc(sizeof(B));
A* a = (A*) malloc(sizeof(C));
B* b = (B*) malloc(sizeof(C));
C* c = malloc(sizeof(C));

// ... use and access members through the pointers

EDIT:

Let me clarify what I'm asking. Is the following use of "multi-level inheritance" guaranteed to work by the C standard?

C* c = malloc(sizeof(C));
// ... initialize fields in c

A* a = (A*) c;
// ... use A fields in a

B* b = (B*) a;
// ... use B fields in b

B* b = (B*) c;
// ... use B fields in b

c = (C*) a;
// ... go back to using C fields in c

回答1:


That the kind of "multi-level inheritance" you describe must work follows from the same principles -- explained in the other Q&A you referenced -- that makes this kind of inheritance work at all. Specifically, the standard explicitly provides that casting the addresses of structures and of their initial members between the applicable types has the desired effect:

A pointer to a structure object, suitably converted, points to its initial member [...] and vice versa.

(paragraph 6.7.2.1/15)

So consider this declaration, relative to the structure definitions provided:

C c;

The quoted provision specifies that &c == (C *) &c.base and (B *) &c == &c.base are both true.

But c.base is a B, so the provision also specifies that (A *) &c.base == &c.base.base and &c.base == (B *) &c.base.base are both true.

Since (B *) &c == &c.base is true and &c.base == (B *) &c.base.base are both true, it follows that (B *) &c == (B *) &c.base.base is also true.

Casting both sides to either A * or C * then produces also the equalities (A *) &c == &c.base.base and &c == (C *) &c.base.base.

This reasoning can be extended to an arbitrary nesting depth.

One can quibble a bit about dynamically allocated structures vis a vis the strict aliasing rule, but there's no reason to think that it is supposed to work any differently in that case, and as long as one first accesses the dynamically-allocated space via an lvalue of the most specific type (C in this example), I see no scenario that supports a different interpretation of the standard for the dynamic-allocation case than applies to other cases. In practice, I do not expect initial access via the most specific type actually to be required by any implementation.




回答2:


What the ISO C standard requires to work is the following situation:

union U {
  struct X x;
  struct Y y;
  struct Z z;
  /* ... */
};

If the structures share some common initial sequence of members, then that initial sequence can be accessed through any of the members. For instance:

 struct X {
   /* common members, same as in Y and Z: */
   int type;
   unsigned flags;

   /* different members */
 };

If all the structures have type and flags in the same order and of the same types, then this is required to work:

union U u;
u.x.type = 42;  /* store through x.type */
foo(u.y.type);  /* access through y.type */

Other hacks of this type are not "blessed" by ISO C.

The situation you have there is a little different. It's question of whether, given a leading member of a structure, can we convert a pointer to the structure to that member's type and then use it. The simplest case is something like this:

struct S {
  int m;
};

Given an object struct S s, we can take the address of m using &s.m, obtaining an int * pointer. Equivalently, we can obtain the same pointer using (int *) &s.

ISO C does require that a structure has the same address as its first member; a pointer to the structure and a pointer to the first member have a different type, but point to the same address, and we can convert between them.

This isn't restricted by nesting levels. Given an a of this type:

struct A {
  struct B {
    struct C {
       int m;
    } c;
  } b
};

the address &a.b.c.m is still the same as the address &a. The pointer &a.b.c.m is the same as (int *) &a.



来源:https://stackoverflow.com/questions/61059895/is-multiple-level-struct-inheritance-guaranteed-to-work-everywhere

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!