3

I have a particular problem in which I have a 16 bit structure. Inside the structure, after the member "x_pos", depending on some external flag, the next 5 bits represent something different.

If the external flag is set, they are rotation & scaling parameters (rotscale_param), else the upper two bits are horizontal and vertical flip bits.

I've tried to represent this structure using a union, but when I so sizeof(attribute), I'm expecting to see 2 bytes, but the result is 4 bytes.

My Code:

typedef unsigned short u16;

struct attribute {
    u16 x_pos : 9;
    union {
        u16 rotscale_param : 5;
        struct {
            u16 unused : 3;
            u16 hflip : 1;
            u16 vflip : 1;
            };
    };
    u16 size : 2;
};

If it helps, I'm trying to make C code for this structure:

OBJ Attribute 
  Bit   Expl.
  0-8   X-Coordinate           
  When Rotation/Scaling used:
    9-13  Rotation/Scaling Parameter Selection
  When Rotation/Scaling not used:
    9-11  Not used
    12    Horizontal Flip      
    13    Vertical Flip        
  14-15 OBJ Size

Source of above quote : http://problemkaputt.de/gbatek.htm#lcdobjoamattributes

Here is a potential solution:

typedef struct attr_flag_set {
    u16 x_pos : 9;
    u16 rotscale_param : 5;
    u16 size : 2;
};

typedef struct attr_flag_unset {
    u16 x_pos : 9;
    u16 unused : 3;
    u16 hflip : 1;
    u16 vflip : 1;
    u16 size : 2;
};

union attribute_1 {
    attr_flag_unset attr_unset;
    attr_flag_set attr_set;
};

I'm not sure if this is the ideal solution, however.

  • You cannot assume your compiler will pack your bitfield structures into the tightest space possible. Check your compiler documentation to see if there might be options to cause this to happen, but it's wrong to assume it will happen automatically (if even your compiler allows it at all). – mah Mar 06 '16 at 17:59
  • @mah: union aside, wouldn't you say it's reasonable to assume that a 9-bit field and a 2-bit field back-to-back in a struct is packed together? – 500 - Internal Server Error Mar 06 '16 at 18:00
  • Possible duplicate of [c union and bitfields](http://stackoverflow.com/questions/3087057/c-union-and-bitfields) – mah Mar 06 '16 at 18:00
  • 1
    @500-InternalServerError It might be intuitively what you expect (and it's a reasonable preference for sure), but I'm afraid it's not reasonable to assume it will happen. C bitfields, it seems, have never received the compiler love they need in order to make them useful for interfacing with hardware (which is often the place they're needed the most). – mah Mar 06 '16 at 18:02
  • Thank you for pointing this out. I'll try to see what I can find on DevkitARM – user6025958 Mar 06 '16 at 18:14

2 Answers2

2

It's not easy to get the correct packing.
Moreover the standard allows bits packing only while the declarations are of the same type.
From ISO/IEC 9899 C99, 6.7.2.1 Structure and union specifiers:

An implementation may allocate any addressable storage unit large enough to hold a bitfield. If enough space remains, a bit-field that immediately follows another bit-field in a structure shall be packed into adjacent bits of the same unit. If insufficient space remains, whether a bit-field that does not fit is put into the next unit or overlaps adjacent units is implementation-defined. The order of allocation of bit-fields within a unit (high-order to low-order or low-order to high-order) is implementation-defined. The alignment of the addressable storage unit is unspecified.

So when you insert a bitfield struct inside a bitfield declaration this breaks the rules and reserves another WORD for storage.
The only workaround is to revert the declaration to a union of 2 different structures:

#pragma pack(1)
union attribute
{
    struct
    {
        u16 x_pos : 9;    //See the homogeneous series of declarations
        u16 rotscale_param : 5;
        u16 size : 2;
    } struct1;
    struct
    {
        u16 x_pos  : 9;    //See the homogeneous series of declarations here also
        u16 unused : 3;
        u16 hflip  : 1;
        u16 vflip  : 1;
        u16 size   : 2;
    } struct2;
};

This works for me under C99-C11.
Don't forget to instruct compiler to pack data. As Mah makes me note this can be accomplished with pragma on most compilers or with a specific syntax on others. And also in this case the packing can be compiler dependent on those border line applications.
P.S. Consider also that bytes order is compiler dependent, but substantially depend on CPU endianess, and for this reason a code that works well on a little-endian machine may fail on a big-endian one.
The best practice would be to use masks and shifts if a fully portable code is desired.

Frankie_C
  • 4,764
  • 1
  • 13
  • 30
  • Are there any cases of `#pragma` not being compiler-specific? – mah Mar 06 '16 at 18:14
  • Your answer makes a lot of sense, thank you. This is also a potential "work around" solution which I thought up of (using two structs inside a union), but I was not sure it'd be the preferred if I was able to make the initial way work out. Much thanks for the enlightenment! – user6025958 Mar 06 '16 at 18:18
  • @mah Yes of course. Thanks for the point. Other compilers can use different syntax (i.e. GCC use `__attribute__(packed)`). But, as you already outlined, this kind of things are close to HW so always to handle with care :) – Frankie_C Mar 06 '16 at 18:19
  • You're welcome, but as I said in the answer the compiler stops filling memory when you change the type, this is by standard, so there is no other way. When you make follow the `u16` declarations with a union the sequence is interrupted and the compiler moves to another BYTE/WORD/DWORD etc. – Frankie_C Mar 06 '16 at 18:22
0

If you are trying to create a bitfiled with hflip vflip, I think you meant to do the following:

struct attribute {
    u16 x_pos : 9;
    union {
        u16 rotscale_param : 5;
        struct {
            u16 unused : 3,
                hflip  : 1,
                vflip  : 1;
        } hvflip;
    };
    u16 size : 2;
};

Where you are actually using unused, hflip and vflip to create a bitfield out of a single unsigned short. Doing as shown in the other answer completely disregards the benefit/purpose of a bitfield by using 3-complete unsigned shorts to represent the 5 bits at issue. You need only 1 unsigned-short to represent 16-bits.

Here is a quick example:

#include <stdio.h>

typedef unsigned short u16;

struct attribute {
    u16 x_pos : 9;
    union {
        u16 rotscale_param : 5;
        struct {
            u16 unused : 3,
                hflip  : 1,
                vflip  : 1;
        } hvflip;
    };
    u16 size : 2;
};

void binprnpad (const unsigned long v, size_t sz);

int main (void) {

    struct attribute attr = { .x_pos = 0b101010101,
                              .rotscale_param = 0b10101, 
                              .size = 0b10 };

    printf ("\n attr.x_pos (%3hu)          : ", attr.x_pos);
    binprnpad (attr.x_pos, 9);

    printf ("\n attr.rotscale_param (%3hu) : ", attr.rotscale_param);
    binprnpad (attr.rotscale_param, 5);

    printf ("\n attr.hvflip.unused (%3hu)  : ", attr.hvflip.unused);
    binprnpad (attr.hvflip.unused, 3);
    printf ("\n attr.hvflip.hflip (%3hu)   : ", attr.hvflip.hflip);
    binprnpad (attr.hvflip.hflip, 1);
    printf ("\n attr.hvflip.vflip (%3hu)   : ", attr.hvflip.vflip);
    binprnpad (attr.hvflip.vflip, 1);

    printf ("\n attr.size (%3hu)           : ", attr.size);
    binprnpad (attr.size, 2);
    printf ("\n\n");

    return 0;
}

void binprnpad (const unsigned long v, size_t sz)
{
    if (!sz) { fprintf (stderr, "error: invalid sz.\n"); return; }
    if (!v)  { putchar ('0'); return; }

    while (sz--)
        putchar ((v >> sz & 1) ? '1' : '0');
}

Output

$ ./bin/struct_nested_bin

 attr.x_pos (341)          : 101010101
 attr.rotscale_param ( 21) : 10101
 attr.hvflip.unused (  5)  : 101
 attr.hvflip.hflip (  0)   : 0
 attr.hvflip.vflip (  1)   : 1
 attr.size (  2)           : 10

Let me know if you have any problems.

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85