Michael Bikovitsky
Michael Bikovitsky

Reputation: 903

C Union of Differently-Sized Structures

Say I have the following structures:

struct StructA
{
    int some_member;
    double some_other_member;
};

struct StructB
{
    char a_char;
    char another_char;
};

Now say I have some buffer in memory that contains a bunch of these structures, and I would like to parse it. Each structure in the buffer is preceded by an int describing the type of the structure. Like this:

struct Descriptor
{
    int type;

    union
    {
        struct StructA a;
        struct StructB b;
    } data;
};

What I would like to do, is to cast the pointer to the buffer (char *) to struct Descriptor *, read the type, then access the correct member of the union, then advance the pointer by the size of the type member + the size of the correct union member (for example, sizeof(int) + sizeof(struct StructA)).

However, will this be valid C code? (In the sense that is does not invoke undefined behaviour.) My main concern is that the memory buffer may be smaller than sizeof(struct Descriptor) because it contains only StructB and the preceding type field.

Is it legal to utilize a pointer to a structure when the underlying memory is not sufficient to hold the entire structure, even though I'm accessing only the valid portion of the memory? Edit: If it is not valid for a structure, is it valid for a union?

If not, is there a better way? I found this comment, and the answer to which it is made, but it deals with an array of the full size of the structure.

Upvotes: 5

Views: 1880

Answers (2)

2501
2501

Reputation: 25753

No, using simple sizeof to calculate the offset is not correct because it doesn't take the possible padding into consideration.

You should use the offsetof macro defined in stddef.h, which will work with any padding:

struct Descriptor* s = /*member data.a is initialized*/;

const size_t aoff = offsetof( struct Descriptor , data.a );
const size_t doff = offsetof( struct StructA , some_other_member );

double* p = ( double* )( ( unsigned char* )s + aoff + doff );

p now points to the member some_other_member in member data.a of s.

Upvotes: 4

Iharob Al Asimi
Iharob Al Asimi

Reputation: 53016

I think it's valid as long as you dereference the pointer correctly, i.e. don't touch the data beyond the buffer size which I assume you know beforehand and deal with alignment properly. So you just need to worry about handling the memory buffer correctly, other than that this is what the standard has to say about such conversion

6.3.2.3 Pointers

  1. A pointer to an object type may be converted to a pointer to a different object type. If the resulting pointer is not correctly aligned68) for the referenced type, the behavior is undefined. Otherwise, when converted back again, the result shall compare equal to the original pointer. When a pointer to an object is converted to a pointer to a character type, the result points to the lowest addressed byte of the object. Successive increments of the result, up to the size of the object, yield pointers to the remaining bytes of the object.

68)In general, the concept ‘‘correctly aligned’’ is transitive: if a pointer to type A is correctly aligned for a pointer to type B, which in turn is correctly aligned for a pointer to type C, then a pointer to type A is correctly aligned for a pointer to type C.

Then, this is what it says about the assignment

6.5.16.1 Simple assignment

Semantics

  1. If the value being stored in an object is read from another object that overlaps in any way the storage of the first object, then the overlap shall be exact and the two objects shall have qualified or unqualified versions of a compatible type; otherwise, the behavior is undefined.

So if the overlap is exact you are just fine and the behavior is defined.

Upvotes: 3

Related Questions