-3

In embedded programming, it is quite often to put sometimes as much as 15 structs in one union. It's a common practice and most people, including me assume it's for saving spaces because the RAM is too small and too precious. But now I wonder, is there any other considerations? For example, speed?

Please enlighten and inform.

Here's an example food for your thought:

typedef struct
{
 union
 { 
  struct{
    ...
  } stru1;


  struct{
    ...
  } stru2;


  struct{
    ...
  } stru3;


  struct{
    ...
  } stru4;

 }
}main_packet
PowerPack1
  • 67
  • 4
  • 1
    How do you think it saves space? It's for completely different reason which is defined in the specs and most likely any tutorial or book explaining C. – Sami Kuhmonen Oct 24 '16 at 04:54
  • 2
    An `union` takes the size of its largest item, so the contrary: you waste a lot of space.... – LPs Oct 24 '16 at 06:08

2 Answers2

4

Using a union to "save space", that is using it to create a variant, is usually considered bad practice (for example banned by MISRA-C) and nothing I would recommend. The presence of a variant type is almost always a sign of bad program design.

The most common purpose of unions is otherwise to use them for type punning, which means that you can write to one union member and then read that data through another member. This is well-defined behavior in C and quite useful when doing hardware-related programming.

For example you can do something like this:

typedef union
{
  uint32_t u32;
  uint8_t  u8 [4];
} my32_t;

my32_t my32;
my32.u32 = 1;

if(my32.u8[0] == 1)
{
  puts("little endian");
}
else
{
  puts("big endian");
}

Unions can also be used to dodge the strict aliasing rules. Code like this would invoke undefined behavior:

uint32_t u32 = 1;
uint16_t u16 = *(uint16_t*)&u32;  // bad, strict aliasing violation

To avoid this bug, the code could be rewritten as:

typedef union
{
  uint32_t u32;
  uint16_t u16;
} something_t;

something_t x;
x.u32 = 1;
uint16_t u16 = x.u16;

This is well-defined behavior as per the exception from strict aliasing listed in 6.5/7:

  • 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)
Community
  • 1
  • 1
Lundin
  • 195,001
  • 40
  • 254
  • 396
  • Sorry for bothering. Your last example depends on endianness, isn't it? I mean `u16` can be `0` – LPs Oct 24 '16 at 06:52
  • @LPs Yes it does. Most often it is therefore better to access certain parts of an integer type through shifts. But I would imagine that there are real scenarios when you would want to read a u32 in u16 chunks. For example I remember writing an eeprom driver where you could only program 16 bits. – Lundin Oct 24 '16 at 06:59
  • Yes, I did it many times, using `uint16_t u16[sizeof(uint32_t)/sizeof(uint16_t)];` – LPs Oct 24 '16 at 07:07
0

Dynamic memory allocation (malloc/free) is the general way to save space, by reusing the memory for data structures whoses lifetime do not overlap. Unions are a very inflexible, hard-to-maintain approach to this purpose. They should only be used in the unusual case where the time overhead of malloc/free is truly unacceptable.

Unions are also used to provide a "primordial" beast that would be a class hierarchy if you were coding in C++. Instead of:

class A { ... };
class B : public A { ... };
class C : public A { ... };
class D : public A { ... };

you write:

struct H
  {
    struct A a;
    enum { Really_B, Really_C, Really_D } really;
    union
      {
        struct B b;
        struct C c;
        struct D d;
      };
  };

In C++ you use virtual functions to get the different behavior needed for B, C, D. In C, you use less maintainable switch or if/else if constructs using the "derived type" identifier field ("really" in this case).

Unions can also be used instead of pointer casts to interpret raw bytes as different types:

typedef union
  {
    float fp;
    struct
      {
        unsigned fraction : 23;
        unsigned exponent : 8;
        unsigned sign     : 1;
      }
    fld;
  }
Floating_point;
WaltK
  • 724
  • 4
  • 13