Accessing bit fields of a structure

L

lancer6238

Hi,

I'm trying to write a structure to represent the header of a GRE
packet, then access each component in the header. However, I have
problems getting the right values for the bit fields.

Here's what I have:

struct grehdrv0
{
#if __BYTE_ORDER == __LITTLE_ENDIAN
unsigned int version:3;
unsigned int flags:5;
unsigned int recur:3;
unsigned int s:1;
unsigned int S:1;
unsigned int K:1;
unsigned int R:1;
unsigned int C:1;
#elif __BYTE_ORDER == __BIG_ENDIAN
unsigned int C:1;
unsigned int R:1;
unsigned int K:1;
unsigned int S:1;
unsigned int s:1;
unsigned int recur:3;
unsigned int flags:5;
unsigned int version:3;
#else
#error "Please fix <bits/endian.h>
u_int16_t protocol;
/* Optional fields */
}

I'm trying to access the bit fields using:

struct grehdrv0 *gre;
....
// gre now points to the gre header portion of the packet
printf("%d %d %d %d %d %d %d %d %x\n", gre->C, gre->R, gre->K, gre->S,
gre->s, gre->recur, gre->flags, gre->version, ntohs(gre->protocol));

The first 16 bits should be (in binary) 0011 0000 1000 0001. However,
I am unable to get the correct values for the first 16 bits of the
header (I got in decimal 1 0 0 0 0 1 6 0), but gre->protocol gives me
the correct value.

How do I access the bit fields?

Thank you.
 
B

Beej Jorgensen

I'm trying to write a structure to represent the header of a GRE
packet, then access each component in the header. However, I have
problems getting the right values for the bit fields.

It's so tempting to just overlay a bitfield struct right onto the packet
data, isn't it? It's perfect! I mean, what could possibly go wrong? :)

The problem is the compiler is free to take extreme liberties with
bitfields, so they're not guaranteed (or perhaps even likely) to be
packed in the way you hope.

C99 6.7.2.1p10:

# 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 all bets are off. It looks like you're accessing the bitfields
correctly when you print them, but the bitfields in the struct aren't
aligning properly with the raw packet data (because the compiler is
allowed to change their alignment, allocation order, etc.)

You're going to have to do it the old-fashioned way and manually check
which bits are set by peering into your char buffer that holds the
packet. Macros might make your life easier in this case.

-Beej
 
J

James Dow Allen

Hi,
#if __BYTE_ORDER == __LITTLE_ENDIAN
#elif __BYTE_ORDER == __BIG_ENDIAN

For your purpose there may be more than two types
of "Endianness". Three or four patterns *might* cover
all cases.
unsigned int ... :3,5,3,1,1,1,1,1

The first 16 bits should be (in binary) 0011 0000 1000 0001

Let's change the spacing in that binary number
and assign fields empirically.

00110 000 1 0 0 0 0 001.
[5]=6 [3]=0 [1]=1 [1]=0 [1]=0 [1]=0 [1]=0 [3]=1
However, ... (I got in decimal 1 0 0 0 0 1 6 0),

I'm surprised you didn't experiment with more data values.
I had little trouble figuring out the straightforward
bit assignment, but with, e.g. a single 1-bit rippling
it would have become very obvious.

The problem is the compiler is free to take extreme liberties with
bitfields, so they're not guaranteed (or perhaps even likely) to be
packed in the way you hope.

[Standard excerpt snipped]

Note that OP's example does *not* have fields spanning an 8-bit
boundary,
and is a nice multiple of 8 bits in length. The standard excerpt
quoted *might* even lead to a *proof* that at most 4 patterns are
possible, but I'll leave that to the C lawyers.
So all bets are off....
You're going to have to do it the old-fashioned way...

I'm certainly no C lawyer, but I'd bet 3 or 4 cases would
cover everything except, possibly, "hypothetical" compilers.
But, rather than coding a painful 'config', it would
be easiest to follow Beej's suggestion and avoid bitfields.

The reason I'm following up is that *I continue to believe*
that some "C lawyers" insist on coding for compilers that
are about as real as the Tooth Fairy. In a recent thread,
I changed code to allow for padding bits. Eventually,
the resident expert admitted machines with such padding
"might be purely hypothetical."

James Dow Allen
 
B

Boon

lancer said:
I'm trying to write a structure to represent the header of a GRE
packet, then access each component in the header. However, I have
problems getting the right values for the bit fields.

Here's what I have:

struct grehdrv0
{
#if __BYTE_ORDER == __LITTLE_ENDIAN
unsigned int version:3;
unsigned int flags:5;
unsigned int recur:3;
unsigned int s:1;
unsigned int S:1;
unsigned int K:1;
unsigned int R:1;
unsigned int C:1;
#elif __BYTE_ORDER == __BIG_ENDIAN
unsigned int C:1;
unsigned int R:1;
unsigned int K:1;
unsigned int S:1;
unsigned int s:1;
unsigned int recur:3;
unsigned int flags:5;
unsigned int version:3;
#else
#error "Please fix <bits/endian.h>
u_int16_t protocol;
/* Optional fields */
}

I'm trying to access the bit fields using:

struct grehdrv0 *gre;
...
// gre now points to the gre header portion of the packet
printf("%d %d %d %d %d %d %d %d %x\n", gre->C, gre->R, gre->K, gre->S,
gre->s, gre->recur, gre->flags, gre->version, ntohs(gre->protocol));

The first 16 bits should be (in binary) 0011 0000 1000 0001. However,
I am unable to get the correct values for the first 16 bits of the
header (I got in decimal 1 0 0 0 0 1 6 0), but gre->protocol gives me
the correct value.

1 0 0 0 0 1 6 0 <-> 10000001 00110000

What does your program print given

struct grehdrv0 foo;
struct grehdrv0 *gre = &foo;
uint8_t *p = (uint8_t *)gre;
p[0] = 0x47;
p[1] = ~0x47;
How do I access the bit fields?

Do you need your code to be portable?

CHAR_BIT > 8 adds a layer of complexity.

Can you assume POSIX? (which mandates CHAR_BIT = 8)

Regards.
 
J

James Kuyper

James Dow Allen wrote:
....
The reason I'm following up is that *I continue to believe*
that some "C lawyers" insist on coding for compilers that
are about as real as the Tooth Fairy.

When I write C code, I write it for compilers which conform fully to the
C standard. I refuse to write C code which make assumptions about
matters that the C standard leaves unspecified unless they are instead
specified as part of the requirements for the program I'm writing. Some
of the assumptions I refuse to make might be violated only by compilers
no more real than the Tooth Fairy; but that doesn't mean I'm actually
coding for such compilers. I suspect that such assumptions are rarer
than you think they are; in many cases, compilers violating the
assumption are only as unreal as gcc 6.0.0 - i.e. they are compilers
that haven't been written yet, but might get written before the lifetime
of my code ends.

I just find it easier to follow a consistent rule, than to evaluate each
possible deviation from that rule for it's likelihood of causing problems.
 
B

Beej Jorgensen

James Dow Allen said:
Note that OP's example does *not* have fields spanning an 8-bit
boundary,

This is a keen observation that I missed; nevertheless, with the
possibility of padding insertion and bit reversal, it's still going to
be "interesting" to get the overlain struct to make sense in all cases.
The standard excerpt quoted *might* even lead to a *proof* that at most
4 patterns are possible, but I'll leave that to the C lawyers.

This would be an interesting thing to see. With the the amount of
padding between struct elements being variable, I would think it would
be difficult to code for.
The reason I'm following up is that *I continue to believe* that some
"C lawyers" insist on coding for compilers that are about as real as
the Tooth Fairy.

Well, at least in this case, that wasn't my intent. :) I distinctly
remember trying to do exactly this thing years ago with packets,
structs, and bitfields, and having it fail.

And I don't have anything against non-portable code when you know what
you're getting into. (I've calculated the length of a function by
subtracting function pointers--and I'm not even going to apologize!) But
in this case, the struct layout in memory could change on the same
platform with the same compiler just by changing the optimization
flags... it seems like it's just asking for trouble--proper trouble--to
code around it.

-Beej
 
B

Ben Bacarisse

Beej Jorgensen said:
This is a keen observation that I missed; nevertheless, with the
possibility of padding insertion and bit reversal, it's still going to
be "interesting" to get the overlain struct to make sense in all
cases.

Padding is not permitted, at least not "in general". Consecutive
fields must be packed into the same storage unit and the
implementation must document the order in which this happens. This is
why James' observation is a good one. If you are writing
non-portable code, you *can* rely on some properties of bit fields.

I agree that they are a potability nightmare, but that is not always
the issue.
This would be an interesting thing to see. With the the amount of
padding between struct elements being variable, I would think it would
be difficult to code for.

Padding of bit fields can only happen when the size is such that it
does not fit in the space remaining in the current "unit". The
implementation must also document what happens in this case and there
are only two possibilities.

And I don't have anything against non-portable code when you know what
you're getting into. (I've calculated the length of a function by
subtracting function pointers--and I'm not even going to apologize!) But
in this case, the struct layout in memory could change on the same
platform with the same compiler just by changing the optimization
flags... it seems like it's just asking for trouble--proper trouble--to
code around it.

Not likely. There would have to be some odd flag like
-use-optimal-9-bit-chars for this to occur and, if it does, the
implementation must document the new packing rules or it will not be
conforming.

Bit fields have huge problems, but they have fewer problems than they
are often portrayed as having!
 
K

Keith Thompson

Beej Jorgensen said:
And I don't have anything against non-portable code when you know what
you're getting into. (I've calculated the length of a function by
subtracting function pointers--and I'm not even going to apologize!) But
in this case, the struct layout in memory could change on the same
platform with the same compiler just by changing the optimization
flags... it seems like it's just asking for trouble--proper trouble--to
code around it.

Varying struct layout depending on compiler flags is permitted (each
set of flags effectively specifies a distinct implementation as
far as the standard is concerned), but it could cause some serious
problems. For example, if a struct is declared in a header file,
then all translation units that include that header, directly or
indirectly, would have to be compiled with the same flags. If the
header is provided as part of a library, then all client code would
have to be compiled with the same flags.

I've seen compiler flags that control whether plain char is signed
or unsigned, and whether pointers are 32 or 64 bits, and such flags
do have to be consistent for all translation units within a program.
But I wouldn't expect an *optimization* flag to affect struct layout.
 
B

Beej

But I wouldn't expect an *optimization* flag to affect struct layout.

I was thinking more "optimize for size", in this case. Like with
gcc's -fpack-struct option. But yeah, I agree it could lead to all
kinds of interesting stuff.

-Beej
 
B

Beej

Bit fields have huge problems, but they have fewer problems than they
are often portrayed as having!

comp.lang.c.bitfields.die.die.die! ;) No, I don't really have a
problem with them in general (you can save tons of space if you're
allocating a pile of structs), but in this particular case with the
packet data, I still think it's more trouble than it's worth trying to
get the struct to line up properly and portably.

-Beej
 

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

Similar Threads


Members online

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,070
Latest member
BiogenixGummies

Latest Threads

Top