Union and struct packing problem

2019-02-10 00:55发布

问题:

I'm writing some software where each bit must be exact(it's for the CPU) so __packed is very important.

typedef union{
uint32_t raw;
struct{
    unsigned int present:1;
    unsigned int rw:1;
    unsigned int user:1;
    unsigned int dirty:1;
    unsigned int free:7;
    unsigned int frame:20;
} __packed;
}__packed page_union_t;

that is my structure and union. It does not work however:

page_union_t p; //.....
//This:
p.frame=trg_page;
p.user=user;
p.rw=rw;
p.present=present;
//and this:
p.raw=trg_page<<12 | user<<2 | rw<<1 | present;

should create the same uint32. But they do not create the same thing.

Is there something I can not see that is wrong with my union?

回答1:

Your struct has only 31 bits



回答2:

AFAIK, the order in which the bits in the struct are stored is undefined by the C99 standard (and the C89 standard too). Most likely, the bits are in the reverse order from what you expected.

You should have shown the result you got as well as the result you expected - it would help us with the diagnosis. The compiler you use and the platform you run on could also be significant.


On MacOS X 10.4.11 (PowerPC G4), this code:

#include <inttypes.h>
#include <stdio.h>

typedef union
{
        uint32_t raw;
        struct
        {
                unsigned int present:1;
                unsigned int rw:1;
                unsigned int user:1;
                unsigned int dirty:1;
                unsigned int free:7;
                unsigned int frame:20;
        };
} page_union_t;

int main(void)
{
        page_union_t p = { .raw = 0 }; //.....
        unsigned trg_page = 0xA5A5A;
        unsigned user = 1;
        unsigned rw = 1;
        unsigned present = 1;

        p.frame = trg_page;
        p.user = user;
        p.rw = rw;
        p.present = present;

        printf("p.raw = 0x%08X\n", p.raw);

        p.raw = trg_page<<12 | user<<2 | rw<<1 | present;
        printf("p.raw = 0x%08X\n", p.raw);

        p.raw <<= 1;
        printf("p.raw = 0x%08X\n", p.raw);
        return(0);
}

produces the results shown:

p.raw = 0xE014B4B4
p.raw = 0xA5A5A007
p.raw = 0x4B4B400E

With the order of the fields reversed, the result is more nearly explicable:

#include <inttypes.h>
#include <stdio.h>

typedef union
{
        uint32_t raw;
        struct
        {
                unsigned int frame:20;
                unsigned int free:7;
                unsigned int dirty:1;
                unsigned int user:1;
                unsigned int rw:1;
                unsigned int present:1;
        };
} page_union_t;

int main(void)
{
        page_union_t p = { .raw = 0 }; //.....
        unsigned trg_page = 0xA5A5A;
        unsigned user = 1;
        unsigned rw = 1;
        unsigned present = 1;

        p.frame = trg_page;
        p.user = user;
        p.rw = rw;
        p.present = present;

        printf("p.raw = 0x%08X\n", p.raw);

        p.raw = trg_page<<12 | user<<2 | rw<<1 | present;
        printf("p.raw = 0x%08X\n", p.raw);

        p.raw <<= 1;
        printf("p.raw = 0x%08X\n", p.raw);
        return(0);
}

This gives the result:

p.raw = 0xA5A5A00E
p.raw = 0xA5A5A007
p.raw = 0x4B4B400E

The first result has an E as the last hex digit because the least significant bit is not used, because the bit-field structure has only 31-bits defined..



回答3:

If the exact position of bits matters, your safest bet is explicit packing and unpacking of the structure into an unsigned char array. Anything else is too implementation dependent.



回答4:

For reference to anyone who might find this, try the packed attribute:

struct __attribute__((packed)){

}


回答5:

You don't mention that you are clearing out the bits of the structure beforehand, are you sure you aren't ending up with garbage bits left over in the first case?

// maybe try this
page_union_t p = {0};