Re: Compatible structs

Discussion in 'C Programming' started by Mark Wooding, Nov 2, 2010.

  1. Mark Wooding

    Mark Wooding Guest

    "Vrtt" <> writes:

    > struct base {
    > int x;
    > char y;
    > };
    >
    > struct extended {
    > int x;
    > char y;
    > double z;
    > };
    >
    > void base_func(struct base *b);
    >
    > struct extended e;
    > base_func((struct base*)&e);
    >
    > Is this actually guaranteed to work, or can struct alignment (or something
    > else) mess it up?


    It's almost, but not quite, guaranteed to work.

    I think there are two problems. One is structure layout compatibility;
    the other is pointer aliasing.

    Let's deal with the layout issue first. The standard tells us
    (6.7.2.1p12) that a pointer to a structure is also a pointer to its
    first element, so there's no padding at the beginning of a structure.
    It doesn't say anything explicitly about subsequent members. However,
    there is a more forthcoming passage which deals with unions (6.5.2.3p5):

    : If a union contains several structures that share a common initial
    : sequence (see below), and if the union object currently contains one
    : of these structures, it is permitted to inspect the common initial
    : part of any of them anywhere that a declaration of the completed type
    : of the union is visible. Two structures share a common initial
    : sequence if corresponding members have compatible types (and, for
    : bit-fields, the same widths) for a sequence of one or more initial
    : members.

    This doesn't quite get us home. It does tell us that if we have a
    declaration

    union both {
    struct base b;
    struct extended e;
    };

    then the layouts of the two structures have to be compatible, since a
    program can take a `struct extended', memcpy it into the `e' of a `union
    both', and hand out a pointer to the `b' of the union. In the absence
    of such a declaration, strictly speaking, all bets are off. But wait:
    this has to work even if some /other/ compilation unit declares `union
    both'! Unless the compiler can see the entire program (or has linker
    connivance) it can't tell whether you've introduced the union somewhere
    where it couldn't see it. A linker which deliberately introduced
    structure layout compatibility because the necessary union declarations
    hadn't been made is allowed but must clearly be deliberately malicious.

    Another way that a compiler might be malicious has occurred to me as I
    type: that structures within unions have magic layout descriptors in
    them which make everything work, while structures elsewhere have
    different descriptors, so code which accesses a `struct base' through a
    pointer actually looks in a different place for the `y' member depending
    on whether the structure came from a `union both' or not. But I don't
    think this is actually possible, because memcpy (or manual fiddling with
    `unsigned char *') has to work.

    Conclusion: declare a union for truly strict conformance, or rely on the
    compiler being non-malicious.

    Now let's deal with aliasing. The standard says (6.5p7):

    : An object shall have its stored value accessed only by an lvalue
    : expression that has one of the following types:
    :
    : -- a type compatible with the effective type of the object,
    :
    : -- a qualified version of a type compatible with the effective type
    : of the object,
    :
    : -- a type that is the signed or unsigned type corresponding to the
    : effective type of the object,
    :
    : -- a type that is the signed or unsigned type corresponding to a
    : qualified version of the effective type of the object,
    :
    : -- an aggregate or union type that includes one of the
    : aforementioned types among its members (including, recursively, a
    : member of a subaggregate or contained union), or
    :
    : -- a character type.

    This would seem to forbid the above kind of pointer games outright. GCC
    certainly emits `strict aliasing' warnings when it sees code like this.

    But we're not finished yet. The union trick can save us. Rather than
    writing a plain old cast, I think that

    union both *b = (union both *)&e;
    base_func(&b->b);

    is fine. I think we can wrap this up in a macro:

    #define PTRCAST(from, to, what) (&((union { \
    from f_; \
    to t_; \
    } *)(what))->t_)

    and say

    base_func(PTRCAST(struct extended, struct base, &e))

    The union really is declared, so structure layouts must be
    compatible; and we've satisfied the aliasing rules by using an lvalue
    (*b, implicit in the `b->b' subexpression) of union type which includes
    a member of compatible type.

    This is a silly and annoying game to have to play, but them's the rules.

    -- [mdw]
    Mark Wooding, Nov 2, 2010
    #1
    1. Advertising

  2. Mark Wooding

    Tim Rentsch Guest

    Mark Wooding <> writes:

    > "Vrtt" <> writes:
    >
    >> struct base {
    >> int x;
    >> char y;
    >> };
    >>
    >> struct extended {
    >> int x;
    >> char y;
    >> double z;
    >> };
    >>
    >> void base_func(struct base *b);
    >>
    >> struct extended e;
    >> base_func((struct base*)&e);
    >>
    >> Is this actually guaranteed to work, or can struct alignment (or something
    >> else) mess it up?

    >
    > It's almost, but not quite, guaranteed to work.
    >
    > I think there are two problems. One is structure layout compatibility;
    > the other is pointer aliasing.
    >
    > Let's deal with the layout issue first. The standard tells us
    > (6.7.2.1p12) that a pointer to a structure is also a pointer to its
    > first element, so there's no padding at the beginning of a structure.
    > It doesn't say anything explicitly about subsequent members. However,
    > there is a more forthcoming passage which deals with unions (6.5.2.3p5):
    >
    > : If a union contains several structures that share a common initial
    > : sequence (see below), and if the union object currently contains one
    > : of these structures, it is permitted to inspect the common initial
    > : part of any of them anywhere that a declaration of the completed type
    > : of the union is visible. Two structures share a common initial
    > : sequence if corresponding members have compatible types (and, for
    > : bit-fields, the same widths) for a sequence of one or more initial
    > : members.
    >
    > This doesn't quite get us home. It does tell us that if we have a
    > declaration
    >
    > union both {
    > struct base b;
    > struct extended e;
    > };
    >
    > then the layouts of the two structures have to be compatible, since a
    > program can take a `struct extended', memcpy it into the `e' of a `union
    > both', and hand out a pointer to the `b' of the union. In the absence
    > of such a declaration, strictly speaking, all bets are off. But wait:
    > this has to work even if some /other/ compilation unit declares `union
    > both'! Unless the compiler can see the entire program (or has linker
    > connivance) it can't tell whether you've introduced the union somewhere
    > where it couldn't see it. A linker which deliberately introduced
    > structure layout compatibility because the necessary union declarations
    > hadn't been made is allowed but must clearly be deliberately malicious.
    >
    > Another way that a compiler might be malicious has occurred to me as I
    > type: that structures within unions have magic layout descriptors in
    > them which make everything work, while structures elsewhere have
    > different descriptors, so code which accesses a `struct base' through a
    > pointer actually looks in a different place for the `y' member depending
    > on whether the structure came from a `union both' or not. But I don't
    > think this is actually possible, because memcpy (or manual fiddling with
    > `unsigned char *') has to work.
    >
    > Conclusion: declare a union for truly strict conformance, or rely on the
    > compiler being non-malicious.


    For truly strict conformance: (1) a union type containing both
    structs must be declared; (2) the structs being dealt with must be
    in an actual instance of that union type; and (3) the definition
    (not just a declaration) of the union type must be visible at the
    point where the member accesses occur. (And then we can access
    members in the "common initial sequence"...)

    > Now let's deal with aliasing. The standard says (6.5p7):
    >
    > : An object shall have its stored value accessed only by an lvalue
    > : expression that has one of the following types:
    > :
    > : -- a type compatible with the effective type of the object,
    > :
    > : -- a qualified version of a type compatible with the effective type
    > : of the object,
    > :
    > : -- a type that is the signed or unsigned type corresponding to the
    > : effective type of the object,
    > :
    > : -- a type that is the signed or unsigned type corresponding to a
    > : qualified version of the effective type of the object,
    > :
    > : -- an aggregate or union type that includes one of the
    > : aforementioned types among its members (including, recursively, a
    > : member of a subaggregate or contained union), or
    > :
    > : -- a character type.
    >
    > This would seem to forbid the above kind of pointer games outright. GCC
    > certainly emits `strict aliasing' warnings when it sees code like this.


    The access is allowed by 6.5.2.3p5; the effective type rules don't
    cause any problems, because the type of the access matches the type
    of the object being accessed. The 'strict aliasing' warnings from
    GCC indicate GCC being overzealous in its optimization, producing
    non-conforming behavior in (some) cases where the behavior is in
    fact defined -- and I believe this is one of them.


    > But we're not finished yet. The union trick can save us. Rather than
    > writing a plain old cast, I think that
    >
    > union both *b = (union both *)&e;
    > base_func(&b->b);
    >
    > is fine. I think we can wrap this up in a macro:
    >
    > #define PTRCAST(from, to, what) (&((union { \
    > from f_; \
    > to t_; \
    > } *)(what))->t_)
    >
    > and say
    >
    > base_func(PTRCAST(struct extended, struct base, &e))
    >
    > The union really is declared, so structure layouts must be
    > compatible; and we've satisfied the aliasing rules by using an lvalue
    > (*b, implicit in the `b->b' subexpression) of union type which includes
    > a member of compatible type.


    This casting does not suffice to define the behavior, both
    because the struct in question is not actually in a union
    object, and because the definition of the union is not
    visible at the point where the member accesses are done
    (ie, in base_func).


    > This is a silly and annoying game to have to play, but them's the rules.


    The special guarantee for unions is there really for a different
    reason, not to let arbitrary struct pointer casting work. Basically
    there are three choices:

    1. Actually put the structs in a union, and follow all the rules;

    2. Put the "base" part in a separate struct type, and have a
    member of that type in the extended struct -- all type safe,
    no casting, no problems; or,

    3. Just cast the pointers (having been careful to make sure the
    two structs in question match up appropriately) and trust that
    everything will work -- don't forget, seat belt laws are for
    wimps and sissies. :)

    In most cases I would advocate (2). In some circumstances it
    might be worth considering (1) or (3).

    (Please excuse my editorializing -- I got started and I just
    couldn't stop...)
    Tim Rentsch, Nov 2, 2010
    #2
    1. Advertising

  3. Mark Wooding

    Eric Sosman Guest

    On 11/2/2010 4:00 PM, Tim Rentsch wrote:
    > [... structs with "common initial sequences" ...]
    > For truly strict conformance: (1) a union type containing both
    > structs must be declared; (2) the structs being dealt with must be
    > in an actual instance of that union type; and (3) the definition
    > (not just a declaration) of the union type must be visible at the
    > point where the member accesses occur. (And then we can access
    > members in the "common initial sequence"...)


    Could you clarify what you mean by "the definition (not just a
    declaration)" of the union type? The language of 6.5.2.3p5 requires
    only a "declaration of the complete type," and says nothing about a
    "definition" thereof.

    --
    Eric Sosman
    lid
    Eric Sosman, Nov 3, 2010
    #3
  4. Mark Wooding

    Tim Rentsch Guest

    Eric Sosman <> writes:

    > On 11/2/2010 4:00 PM, Tim Rentsch wrote:
    >> [... structs with "common initial sequences" ...]
    >> For truly strict conformance: (1) a union type containing both
    >> structs must be declared; (2) the structs being dealt with must be
    >> in an actual instance of that union type; and (3) the definition
    >> (not just a declaration) of the union type must be visible at the
    >> point where the member accesses occur. (And then we can access
    >> members in the "common initial sequence"...)

    >
    > Could you clarify what you mean by "the definition (not just a
    > declaration)" of the union type? The language of 6.5.2.3p5 requires
    > only a "declaration of the complete type," and says nothing about a
    > "definition" thereof.


    I meant that the union type's contents must be defined, in the
    sense of section 6.7.5.3 (eg, p6), which condition is the same
    as there being a declaration of the complete type (as explained
    in p4). Perhaps a poor choice of wording on my part; I assumed
    people would understand I was talking about the type's contents
    needing to be defined.
    Tim Rentsch, Nov 3, 2010
    #4
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Patricia  Van Hise

    structs with fields that are structs

    Patricia Van Hise, Apr 5, 2004, in forum: C Programming
    Replies:
    5
    Views:
    614
    Al Bowers
    Apr 5, 2004
  2. Chris Hauxwell

    const structs in other structs

    Chris Hauxwell, Apr 23, 2004, in forum: C Programming
    Replies:
    6
    Views:
    539
    Chris Hauxwell
    Apr 27, 2004
  3. Ben Bacarisse

    Re: Compatible structs

    Ben Bacarisse, Nov 2, 2010, in forum: C Programming
    Replies:
    2
    Views:
    251
    Tim Rentsch
    Nov 2, 2010
  4. Eric Sosman

    Re: Compatible structs

    Eric Sosman, Nov 2, 2010, in forum: C Programming
    Replies:
    22
    Views:
    708
    Francis Moreau
    Nov 9, 2010
  5. pantagruel
    Replies:
    0
    Views:
    219
    pantagruel
    Feb 17, 2006
Loading...

Share This Page