views:

439

answers:

5

Is there a portable way in C to find out the mask for a bit field at compile time?

Ideally, I'd like to be able to atomically clear a field like this:

struct Reference {
    unsigned age : 3;
    unsigned marked : 1;
    unsigned references : 4;
};

struct Reference myRef;
__sync_and_and_fetch(&myRef, age, ~AGE_MASK);

Otherwise I have to take out a lock on the struct, which is more heavyweight than I'd like.

+1  A: 

I don't think is possible - even with offsetof() which works for byte offsets but does not seem to work for bitfields. I would redeclare the fields as enums/defines (0x01 0x02 etc) and manage the bits yourself, so you can get your atomic changes.

dajobe
+2  A: 

You could do something like:

union Reference {
  unsigned asWord;
  struct {
    unsigned age : 3;
    unsigned marked : 1;
    unsigned references : 4;
  } asFields;
}

To atomically clear a field of myRef, do

union Reference myRef;

union Reference oldr = myRef;
union Reference newr = oldr;
newr.asFields.age = 0;
compare_and_swap(&myRef.asWord, oldr.asWord, newr.asWord);

(+ unshown code to handle when compare_and_swap fails)

Keith Randall
+2  A: 

I don't know how to do it at compile time, but at runtime it should be a simple matter of union'ing an instance of your bitfield struct with an appropriately-sized unsigned int, and setting all fields to 0 except the one you care about which should be set to all 1s -- the value of the unsigned int is then the bitmask you want. You can do it for each field at startup, maybe with a macro to avoid some repetitive code. Might not that suffice?

Alex Martelli
+1 I can imagine this working. There may be hairy issues with endianness and bitfields, but I can imagine this being safe. But I'm not a standards guru.
Chris Lutz
+2  A: 

Or, if you really wanted the mask:

union Reference {
  unsigned asWord;
  struct {
    unsigned age : 3;
    unsigned marked : 1;
    unsigned references : 4;
  } asFields;
}

Reference agemask_ref;
agemask_ref.asFields = (typeof(agemask_ref.asFields)){0, -1, -1};
unsigned agemask = agemask_ref.asWord;
Keith Randall
Note that `typeof()` is a GCC extension and is not portable to other compilers.
Chris Lutz
Thank you Keith - nice hack.
Grandpa
Out of curiousity, why is this accepted if it is non-portable? Just curious.
BobbyShaftoe
TThat whole line is a gcc extension because it is a constructor expression. But it is trivial to replace it with 3 assignments, one for each field.
Keith Randall
typeof() is going into C++0x if you're allowed/willing to make use of a C++ feature
Joseph Garvin
A: 

Yes, this can be done. You need to capture the value and do the operation. Then you need to use an atomic compare and swap (like InterlockedCompareExchange on Windows) to store the new value if the memory still contains the old value. If someone modified the value, you loop and try again. Note this is a standard pattern for doing any operation on a word-sized piece of data where an intrinsic isn't available.

The code below uses an int - as Keith pointed out you can use a union to be able to get the values of the struct as an int.

int oldValue, newValue;
do
{
    oldValue = myRef;
    newValue = oldValue & ~AGE_MASK;
} while (InterlockedCompareExchange(&myRef, newValue, oldValue) != oldValue);
Michael