Search code examples
cstructunions

How to access C structs with a union within a struct


I am working with someone else's C code, where they have defined the following:

typedef struct {

  union{

    struct{
      int A;    // some data
     } structA;

    struct{
       char B;  // some alternative data
    } structB;

  } myUnion;

} myStruct;

Youch. Earlier in the code, these mega-structs are declared, malloc'ed, and populated with data. I am working on a later section of code where I'll be passed a pointer to one of these structs and have to (A) determine if type structA or structB was used, and then (B) read the actual data. Something like:

void myFunction(myStruct *s){

   if(s->myUnion.structA != NULL)      // compilation error here
      printf("This myStruct uses a structA, internal data is: %d\n", s->myUnion.structA.A);
   else
      printf("This myStruct uses a structB, internal data is: %c\n", s->myUnion.structB.B);

}

Obviously the above doesn't compile:

me@Linux:/home/me# gcc -Wall structsUnions.c
structsUnions.c: In function 'myFunction':
structsUnions.c:22:19: error: invalid operands to binary != (have 'struct <anonymous>' and 'void *')
   if(s->myUnion.structA != NULL)
                         ^
me@Linux:/home/me#

But I'm at wit's end trying to figure out the syntax here.

There's got to be a way of peaking into a myStruct and determining if a structA or structB is inside the myUnion.

Any thoughts?


Solution

  • Since the code you shared doesn't include a tag field indicating whether the A or B member is actually used, there's no concrete way of knowing for sure which one was intended in any particular instance.

    However, depending on the data you're getting, you may be able to make an educated guess in your code. For example, say I have the following union on a system that uses IEEE 754 for floating point types:

    typedef union _F_or_I
    {
        float f;
        int32_t i;
        uint32_t bits;
    } F_or_I;
    

    If the first 9 bits (the sign and the exponent) are all 0, it's probably not a floating point number:

    F_or_I foo = /* some value */;
    
    if(!(foo.bits & 0xFF800000))
    {
          // access member 'i'
    }
    else
    {
          // access member 'f'
    }
    

    Of course, this way is not always accurate depending on the exact type(s) you're using in the union (I wouldn't even rely on it for the types used in my example!), and the correct way to do this is to include a 'tag' member in the parent struct that indicates which member of the union is intended to be accessed.