2

I tried to use a structure with different sized bit-fields. The total number of bits used is 64. However, when I check the structure size, I get 11 instead of an expected 8. By trying to decompose the structure, I saw the difference came from the day field. If I pack every bit to get 8-bits packs, the day field is packed beetween the "end" of month and the "start" of hour. I don't know if this is a good approach. Can someone explain me that ?

typedef unsigned char uint8_t;

typedef struct frameHeader_t
    {
        uint8_t encryption       : 2;
        uint8_t frameVersion     : 2;
        uint8_t probeType        : 4;
        uint8_t dataType         : 5;
        uint8_t measurePeriod    : 3;
        uint8_t remontePerdiod   : 4;
        uint8_t nbrMeasure       : 2;
        uint8_t year             : 7;
        uint8_t month            : 4;
        uint8_t day              : 5;
        uint8_t hour             : 5;
        uint8_t minute           : 6;
        uint8_t second           : 6;
        uint8_t randomization    : 5;
        uint8_t status           : 4;

    }FrameHeader;

int main()
{

    FrameHeader my_frameHeader;
    printf("%d\n", sizeof(FrameHeader));
    return 0;
}
4
  • 3
    You probably need #pragma pack or similar (depending on your compiler, or more precisely, compiler's preprocessor). The year field "lands" on two bytes, so most likely it is padded by the compiler. Subsequently, some of the fields which follow it also "land" on two bytes, so more padding is applied. Commented Jun 26, 2019 at 14:09
  • The problem is this: "...an expected 8". There is no expected size of a bit-field. They can't be reliably used for memory-mapping. Commented Jun 26, 2019 at 14:11
  • 1
    In pure C, there's no guarantee about the memory layout of a struct. Commented Jun 26, 2019 at 14:18
  • 1
    In C, the traditional way to pack bit fields is to pack them inside integers whose size is the same as the declared integer type. So in your example, they would be packed into 8-bit integers. If a bit field didn't fit, it would skip to a new integer. I believe the packing requirements have been loosened up since then, but it is still a common strategy. So in your case the packings are (1) 2, 2, 4 (2) 5, 3 (3) 4, 2 (4) 7 (5) 4 (6) 5 (7) 5 (9) 6 (9) 6 (10) 5 (11) 4. So you're ending up with 11 8-bit integers. If you want improved packing, switch to a larger integer type, e.g. uint32_t. Commented Jun 26, 2019 at 14:46

2 Answers 2

2

If you run it through the pahole tool, you should get an explanation:

struct frameHeader_t {
    uint8_t                    encryption:2;         /*     0: 6  1 */
    uint8_t                    frameVersion:2;       /*     0: 4  1 */
    uint8_t                    probeType:4;          /*     0: 0  1 */
    uint8_t                    dataType:5;           /*     1: 3  1 */
    uint8_t                    measurePeriod:3;      /*     1: 0  1 */
    uint8_t                    remontePerdiod:4;     /*     2: 4  1 */
    uint8_t                    nbrMeasure:2;         /*     2: 2  1 */

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

    uint8_t                    year:7;               /*     3: 1  1 */

    /* XXX 1 bit hole, try to pack */

    uint8_t                    month:4;              /*     4: 4  1 */

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

    uint8_t                    day:5;                /*     5: 3  1 */

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

    uint8_t                    hour:5;               /*     6: 3  1 */

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

    uint8_t                    minute:6;             /*     7: 2  1 */

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

    uint8_t                    second:6;             /*     8: 2  1 */

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

    uint8_t                    randomization:5;      /*     9: 3  1 */

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

    uint8_t                    status:4;             /*    10: 4  1 */

    /* size: 11, cachelines: 1, members: 15 */
    /* bit holes: 8, sum bit holes: 20 bits */
    /* bit_padding: 4 bits */
    /* last cacheline: 11 bytes */
};

You're using uint8_t as the base type so the fields are getting padded to groups of 8 bits.

You should be able to completely eliminate the padding, somewhat more portably than with __attribute((packed)) by using unsigned long long/uint_least64_t (at least 64 bits large) as the base type of the bitfields, but technically the non-int/non-unsigned-int base types for bitfields aren't guaranteed to be supported, but you could use unsigned (at least 16 bits guaranteed by the C standard) after reorganizing the bitfields a little, for example into:

typedef struct frameHeader_t
{
    //16
    unsigned year             : 7;
    unsigned randomization    : 5;
    unsigned month            : 4;

    //16
    unsigned second           : 6;
    unsigned minute           : 6;
    unsigned status           : 4;

    //16
    unsigned hour             : 5;
    unsigned dataType         : 5;
    unsigned probeType        : 4;
    unsigned encryption       : 2;

    //16
    unsigned day              : 5;
    unsigned remontePerdiod   : 4;
    unsigned measurePeriod    : 3;
    unsigned nbrMeasure       : 2;
    unsigned frameVersion     : 2;

}FrameHeader; 
//should be an unpadded 8 bytes as long as `unsigned` is 16,
//32, or 64 bits wide (I don't know of a platform where it isn't)

(The padding or lack thereof isn't guaranteed, but I've never seen an implementation insert it unless it was necessary.)

Sign up to request clarification or add additional context in comments.

3 Comments

the problem with your solution is that it does not preserve the fields order making it useless when the received data fave its own order.
@P__J__ I do mention that using unsigned long long/uint_least64_t should fix it without the need to reorder.
@P__J__ For IO, I explicit bit manipulation from a uin8_t array should be the most portable approach.
0

to avoid the packing magic I usually use fixed size unsigned types with the same size

typedef struct frameHeader_t
    {
        uint64_t encryption       : 2;
        uint64_t frameVersion     : 2;
        uint64_t probeType        : 4;
        uint64_t dataType         : 5;
        uint64_t measurePeriod    : 3;
        uint64_t remontePerdiod   : 4;
        uint64_t nbrMeasure       : 2;
        uint64_t year             : 7;
        uint64_t month            : 4;
        uint64_t day              : 5;
        uint64_t hour             : 5;
        uint64_t minute           : 6;
        uint64_t second           : 6;
        uint64_t randomization    : 5;
        uint64_t status           : 4;

    }FrameHeader;

https://godbolt.org/z/BX2QsC

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.