0

I have a 64bit integer that is used as a handle. The 64bits must be sliced into the following fields, to be accessed individually:

size           : 30 bits
offset         : 30 bits
invalid flag   : 1 bit
immutable flag : 1 bit
type flag      : 1 bit
mapped flag    : 1 bit

The two ways I can think of to achieve this are:

1) Traditional bit operations (& | << >>), etc. But I find this a bit cryptic.

2) Use a bitfield struct:

#pragma pack(push, 1)
struct Handle {
    uint32_t size      : 30;
    uint32_t offset    : 30;
    uint8_t  invalid   : 1;
    uint8_t  immutable : 1;
    uint8_t  type      : 1;
    uint8_t  mapped    : 1;
};
#pragma pack(pop)

Then accessing a field becomes very clear:

handle.invalid = 1;

But I understand bitfields are quite problematic and non-portable.

I'm looking for ways to implement this bit manipulation with the object of maximizing code clarity and readability. Which approach should I take?

Side notes:

  • The handle size must not exceed 64bits;

  • The order these fields are laid in memory is irrelevant, as long as each field size is respected;

  • The handles are not saved/loaded to file, so I don't have to worry about endianess.

pnuts
  • 58,317
  • 11
  • 87
  • 139
glampert
  • 4,371
  • 2
  • 23
  • 49
  • 1
    I wouldn't say bit fields are 'problematic', and you haven't listed portability as a requirement, but they do generate code at a rate of knots that you could write better yourself, for example clearing all fields to zero, or setting or clearing two fields at the same time. Unless you have severe code space constraints it really comes down to what kind of code you want to write. – user207421 Jul 05 '14 at 04:34
  • 2
    Given your requirements, the bitfield seems like the simplest solution. The non-portability comes from how different compilers may order them differently. But you should use `uint64_t` for all of them. – M.M Jul 05 '14 at 05:02
  • The point of a "handle" type is that it is opaque to any code that stores the handle. Only the code that creates the handle ever interprets its content. Which of course means that portability is of no concern. – Hans Passant Jul 05 '14 at 08:59

2 Answers2

3

I would go for the bitfields solution.

Bitfields are only "non-portable" if you want to store the in binary form and later read the bitfield using a different compiler or, more commonly, on a different machine architecture. This is mainly because field order is not defined by the standard.

Using bitfields within your application will be fine, and as long as you have no requirement for "binary portability" (storing your Handle in a file and reading it on a different system with code compiled by a different compiler or different processor type), it will work just fine.

Obviously, you need to do some checking, e.g. sizeof(Handle) == 8 should be done somewhere, to ensure that you get the size right, and compiler hasn't decided to put your two 30-bit values in separate 32-bit words. To improve the chances of success on multiple architectures, I'd probably define the type as:

struct Handle {
    uint64_t size      : 30;
    uint64_t offset    : 30;
    uint64_t invalid   : 1;
    uint64_t immutable : 1;
    uint64_t type      : 1;
    uint64_t mapped    : 1;
};

There is some rule that the compiler should not "split elements", and if you define something as uint32_t, and there are only two bits left in the field, the whole 30 bits move to the next 32-bit element. [It probably works in most compilers, but just in case, using the same 64-bit type throughout is a better choice]

Mats Petersson
  • 126,704
  • 14
  • 140
  • 227
  • Thanks @MatsPetersson, I got the data types wrong, I thought it was proper to use the type that most closely matched the field size, which forced me to pack the struct with the `#pragmas`. Using only `uint64_t` keeps the struct size = 8, without need for packing. And sure, I intend to use a "compile time assert". – glampert Jul 05 '14 at 18:16
2

I recommend bit operations. Of course you should hide all those operations inside a class. Provide member functions to perform set/get operations. Judicious use of constants inside the class will make most of the operations fairly transparent. For example:

bool Handle::isMutable() const {
    return bits & MUTABLE;
}

void Handle::setMutable(bool f) {
    if (f) 
        bits |= MUTABLE;
    else 
        bits &= ~MUTABLE;
}
Dwayne Towell
  • 8,154
  • 4
  • 36
  • 49
  • This strikes me as poor advice. Within the stated constraints, writing the code to shift and mask on your own seems to provide precisely zero advantage, even at very best. – Jerry Coffin Jul 05 '14 at 05:22