3

I'm working with C structs including bit-fields such as this one:

struct beeper_general_t
{
  uint1_t enable : 1;
  uint32_t : 7;
  enum2_t loudness : 2;
  uint32_t : 22;
  enum2_t status : 2;
};

For debugging purposes, I need to know how the compiler (GCC) lays out the struct in memory (exact position and width of each field).

What I'm doing right now is write some test code like this one:

struct beeper_general_t my_struct;
for(;;) {
    my_struct.enable = 0;
    my_struct.enable = 1;
}

Then I look at the generated assembly code to get the information I want. As this is a rather tedious process, I wonder whether there's a simpler way of visualizing the actual layout of structs in memory.

Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985
geschema
  • 2,464
  • 4
  • 30
  • 41

4 Answers4

1

I hope all types uint1_t, uint32_t etc are same to exploit the bitfields properly. If not, you should make it same as:

struct beeper_general_t
{
  uint32_t enable : 1;
  uint32_t : 7;
  uint32_t loudness : 2;
  uint32_t : 22;
  uint32_t status : 2;
};

6.7.2.1 Structure and union specifiers

[10] 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.

If you are want a specific layout, please prefer bit shifting as layout of bitfields is unspecified and may change with compiler, target etc.

If you want enable to be 1 bit, loudness to be 2 bit etc, using bitfield is a wise choice. If you want enable to be specifically at bit0 or bit31 or for similar requirement, you should avoid bitfield.

Mohit Jain
  • 30,259
  • 8
  • 73
  • 100
  • Thanks, but my question was more about how to get information about the struct layout that was chosen by the compiler. It's clear to me that almost everything about bit-fields is implementation-dependend, and that they should generally be avoided. – geschema Jul 31 '14 at 13:34
  • OK, then I would prefer Method1: dump memory around my_struct(in program or using GDB), Method2: use `-S` switch and analyze the difference in assembly by incrementally setting the bitfields. – Mohit Jain Jul 31 '14 at 14:05
1

The generated assembly, or alternatively the compiler documentation are the typical way to go about this. It wouldn't be a big deal to write a function that dumps the memory occupied by a struct along with the address offsets of all the members, but you'd need to redo that for every new struct.

a2800276
  • 3,272
  • 22
  • 33
1

pahole is useful for the purpose of viewing struct layout. Sadly because uint1_t and enum2_t aren't standard types I have to substitute something else in but here's some example output:

struct beeper_general_t
{
        uint32_t enable : 1;
        uint16_t : 7;
        uint32_t loudness : 2;
        uint64_t : 22;
        uint32_t status : 2;
};

Results in this pahole output:

struct beeper_general_t {
    uint32_t                   enable:1;             /*     0:31  4 */
    uint32_t                   loudness:2;           /*     0:22  4 */

    /* XXX 29 bits hole, try to pack */

    uint32_t                   status:2;             /*     4:30  4 */

    /* size: 8, cachelines: 1, members: 3 */
    /* bit holes: 1, sum bit holes: 29 bits */
    /* bit_padding: 30 bits */
    /* last cacheline: 8 bytes */
};

Similar questions:

Community
  • 1
  • 1
Anon
  • 339
  • 5
  • 8
0

The present version of the C standard defines bitfield layout too strictly to allow compilers to lay them out optimally, but without sufficient detail to allow code to do much of anything if the standards said nothing about their layout.

A definition like unsigned short x:5; implies that if there is no previous structure element, the previous structure element was not a bitfield whose declared type was a short or unsigned short, or the space holding it doesn't have space for another five-bit value, the compiler should create a new unnamed structure element of type unsigned short and allocate a range of five consecutive bits to x. If none of those conditions apply (i.e. there is a previous structure element, it's a bitfield whose declared type is short or unsigned short, and the space holding it has room for another five-bit bitfield), the bitfield will share the element with the previous one.

Thus, given a declaration like:

struct {
  uint16_t a,b,c,d,e,f,g,h : 6;
} PACK_AS_UINT16;

a compiler would start by placing a in a 16-bit word and then putting b somewhere in that same word. Because c won't fit in the same word (that would bring the total to 18 bits) the compiler must allocate another word for c, but can pack d in there as well. Likewise another word for e and f, and a fourth one for g and h. If the declaration had instead been:

struct {
  uint32_t a,b,c,d,e,f,g,h : 6;
} PACK_AS_UINT32:

then the compiler would be able to pack a-e in one 32-bit word, and would put f-h in another (leaving 14 bits of unused space in the second word).

Note that while the optimal packing for eight 6-bit values would use three 16-bit words, such a packing would require that two of the values straddle 16-bit items. While some compilers have historically been able to accommodate that without difficulty, the present standard does not permit it.

supercat
  • 77,689
  • 9
  • 166
  • 211