bitfield organization

D

David Resnick

I'm a bit confused about how bitfield organization works.

In the following code:

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

/* Layout of structure
* 7 6 5 4 3 2 1 0
*| PDU | Frame |
*| FQC | RFCI |
*| Header CRC | Pay
* load CRC |

i.e.
4 bits PDU
4 bits Frame
2 bits FQC
6 bits RFCI
6 bits Header CRC
10 bits Payload CRC
*/

int main(void)
{
unsigned int i;
typedef struct
{
uint8_t frame:4; // The frame number
uint8_t PDUType:4; // The PDU type, we support 0 for media
or 14 for ctrl
uint8_t RFCI:6; // Radio Access Bearer Sub Flow
Indicator (helpful?)
uint8_t FQC:2; // Frame Quality Classification
uint16_t HeaderCRC:6; // CRC for checking header integrity
uint16_t PayloadCRC:10; // CRC for checking payload integrity

} IU_UP_PDU_TYPE_0;

IU_UP_PDU_TYPE_0 blah;

memset(&blah, '\0', sizeof blah);

blah.HeaderCRC = 0x3F;

unsigned char *pBlah = (unsigned char*)&blah;
for ( i = 0; i < sizeof blah; ++i )
{
printf("%02X ", (unsigned int) pBlah);
}

printf("\n");

return 0;
}

I can't see a way to get the Header CRC to occupy the correct bits in
the overall arrangement.

temp(641)$ gcc -Wall -o foo foo.c
temp(642)$ foo
00 00 3F 00

As you can see, it occupies the 6 LSB of the uint16_t.

I gather bitfields aren't very portable. Any wisdom on the above?

Thanks,
-David
 
D

David Resnick

I'm a bit confused about how bitfield organization works.

In the following code:

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

/* Layout of structure
 * 7  6  5  4  3  2  1  0
 *|  PDU      | Frame   |
 *| FQC |     RFCI       |
 *| Header CRC   | Pay
 *  load CRC             |

 i.e.
  4 bits PDU
  4 bits Frame
  2 bits FQC
  6 bits RFCI
  6 bits Header CRC
 10 bits Payload CRC
 */

int main(void)
{
    unsigned int i;
    typedef struct
    {
        uint8_t frame:4;       // The frame number
        uint8_t PDUType:4;     // The PDU type, we support 0 for media
or 14 for ctrl
        uint8_t RFCI:6;        // Radio Access Bearer Sub Flow
Indicator (helpful?)
        uint8_t FQC:2;         // Frame Quality Classification
        uint16_t HeaderCRC:6;   // CRC for checking header integrity
        uint16_t PayloadCRC:10; // CRC for checking payload integrity

    } IU_UP_PDU_TYPE_0;

    IU_UP_PDU_TYPE_0 blah;

    memset(&blah, '\0', sizeof blah);

    blah.HeaderCRC = 0x3F;

    unsigned char *pBlah = (unsigned char*)&blah;
    for ( i = 0; i < sizeof blah; ++i )
    {
        printf("%02X ", (unsigned int) pBlah);
    }

    printf("\n");

    return 0;

}

I can't see a way to get the Header CRC to occupy the correct bits in
the overall arrangement.

temp(641)$ gcc  -Wall -o foo foo.c
temp(642)$ foo
00 00 3F 00

As you can see, it occupies the 6 LSB of the uint16_t.

I gather bitfields aren't very portable.  Any wisdom on the above?

Thanks,
-David


After looking at it for a bit, doesn't seem to work to have those 2
LSB of the 3rd byte want to be attached to the 8 bits of the 4th
byte. So I'm doing this, seem reasonable?

typedef struct
{
uint8_t frame:4; // The frame number
uint8_t PDUType:4; // The PDU type, we support 0 for
media or 14 for ctrl
uint8_t RFCI:6; // Radio Access Bearer Sub Flow
Indicator (helpful?)
uint8_t FQC:2; // Frame Quality Classification
uint8_t PayloadCRCMSB:2; // the 2 MSB of the CRC for checking
payload integrity
uint8_t HeaderCRC:6; // CRC for checking header integrity
uint8_t PayloadCRCLSB:8; // the 8 LSB of the CRC for checking
payload integrity

} IU_UP_PDU_TYPE_0;
 
N

Nick Keighley

I'm a bit confused about how bitfield organization works.

I gather bitfields aren't very portable.  Any wisdom on the above?

bitfields aren't very portable it's a bit of a git. I couldn't work
out from your example what you were tryting to do. If you want to use
bitfields then you need to consult your compiler documentation (if
there is any!) on how they're laid out in memory. Otherwise you're
going to have to experiment.

You might end up writing (or getting one off the internet) a bit read/
writer library using shifts and masks. If *that's* too slow then write
a code generator to generate the appropriate shifts and masks. A bit
of a pain but you should only need to do it once..
 
P

Philipp Klaus Krause

Am 27.05.2010 16:32, schrieb David Resnick:
I gather bitfields aren't very portable. Any wisdom on the above?

Isn't the use of uint8_t, uint16_t in bitfields already nonportable?
AFAIK these should be "unsigned int" (and a bit confusing, if you ever
need a signed one you have to use "signed int", "int" won't do).

Philipp
 
E

Eric Sosman

I'm a bit confused about how bitfield organization works.

Don't feel bad: It's inherently confusing, because the compiler
has wide latitude in how it arranges things and the language imposes
very few restrictions.

Bit-fields reside in unnamed "addressable storage units," whose
size and nature are entirely up to the compiler -- and the compiler
is not even required to document how it makes its choice. Here's
all we're guaranteed about the layout of bit-fields in ASU's:

- The ASU containing a particular bit-field is after the bytes
occupied by non-bit-field elements named earlier, and before
any non-bit-field elements named later. That is, the ASU's
are positioned as you'd expect, relative to non-bit-fields
in the same struct.

- If the struct contains two or more consecutive bit-fields,
the ASU containing the earlier bit-field does not appear
after the ASU containing a later bit-field. That is, ASU's
are allocated to bit-fields in non-decreasing order.

- If the struct contains two or more consecutive bit-fields,
they'll be packed into the same ASU as long as there's room.
Unfortunately, since there's no way to discover how big an
ASU is, you can't draw many conclusions from this.

- If the struct contains two or more consecutive bit-fields
and a later bit-field is too big for an ASU that's already
partially filled, the compiler may start over with a fresh
ASU (leaving "padding bits" in the old one) or may split the
too-big bit-field across two or more ASU's. As before, since
the compiler needn't disclose what packing strategy it uses
nor how big an ASU is, this doesn't tell you much.

- You can force the compiler to skip to a new ASU for the next
bit-field, by using a nameless bit-field of width :0. But
(broken record) since you don't know how big an ASU is ...

- The arrangement of bits within an ASU is entirely up to the
compiler. It can allocate successive bit-fields from high-
order bits to low, or from low to high, or from the ends
toward the middle, or any way it pleases. Same with padding
bits: If there are any, they can be anywhere. And, as usual,
the compiler needn't tell you how it chooses an arrangement.

- A single bit-field contains no padding bits, and if it's signed
it uses two's complement representation. But the arrangement
of bits may be big-endian, little-endian, middle-endian, or
end-over-endian -- and would you care to guess whether the
compiler is obliged to document its choice?
I gather bitfields aren't very portable. Any wisdom on the above?

Like the structs (or unions) that contain them, bit-fields are
entirely portable[*] as a means to represent data within the program.
And like the structs (or unions) that contain them, bit-fields are
*not* suitable for matching externally-imposed formats. If somebody
tells you about a packet containing a three-bit field, a five-bit
field, and two four-bit fields, it's tempting to try to map it with
bit-fields of :3, :5, :4, :4 bits. You may even get the mapping to
work as desired with the C compiler on your favorite machine.

... but move the code to a different compiler, and things may
well change. The individual bit-field elements will still be there,
will still have their names, and will still be able to hold the
same values, but there's very little you can be sure of about how
they're laid out.

[*] Well, "almost perfectly portable." If you just say int
rather than signed int or unsigned int, the compiler gets to choose
whether your bit-field is signed or not -- and different compilers
may choose differently. Also, the only portable "base types" for a
bit-field are the int flavors plus _Bool (C99); compilers may accept
other types like short and uint8_t, but are not obliged to -- so if
you use those other types with one compiler, the next might not accept
them. Finally, a bit-field's width cannot exceed that of its base
type -- so if you try a :24 bit-field on a machine with 16-bit int,
you're out of luck. BUT, if you stick to signed int, unsigned int,
and _Bool while avoiding other types (including plain int), and if
you keep all the widths to :16 or less, bit-fields are "perfectly
portable."[**]

[**] This is supposed to be a comfort. Stop weeping.
 
D

David Resnick

     Don't feel bad: It's inherently confusing, because the compiler
has wide latitude in how it arranges things and the language imposes
very few restrictions.

Thanks very much for this (and the other) responses.

Portable or not, I clearly can't represent as a single bitfield bits
that are not contiguous. Since the underlying data layout treats 10
bits that include the 2 LSB of one byte and the 8 bits of the next
byte as a single entity, I just need to have two separate fields.

And yes, this is a portability mess to treat the underlying data
layout as a bitfield...

-David
 
S

Stargazer

 If somebody
tells you about a packet containing a three-bit field, a five-bit
field, and two four-bit fields, it's tempting to try to map it with
bit-fields of :3, :5, :4, :4 bits.  You may even get the mapping to
work as desired with the C compiler on your favorite machine.

     ... but move the code to a different compiler, and things may
well change.  The individual bit-field elements will still be there,
will still have their names, and will still be able to hold the
same values, but there's very little you can be sure of about how
they're laid out.

Actually, there may be surprises even when moving with the same
compiler to another architecture. E.g. the ways that GCC arranges bit-
fields on x86 and big-endian ARM are quite different.

Daniel
 
E

Eric Sosman

Actually, there may be surprises even when moving with the same
compiler to another architecture. E.g. the ways that GCC arranges bit-
fields on x86 and big-endian ARM are quite different.

It's not "the same compiler" if it compiles for a different
architecture. It's not even "the same compiler" if you run it for
the same target architecture but with different option flags; for
example, "gcc" and "gcc -ansi" and "gcc -std=c99" are different
compilers.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,009
Latest member
GidgetGamb

Latest Threads

Top