3

I have a bunch of properties crammed in a bitfield to save on space:

struct Flags {
    uint access : 2;
    uint status : 2;
    uint isEnabled : 1;
    uint isDeletable: 1;
    ...
};

Then I have a static Flags defaultFlags which is initialized on program startup. My main question is whether it is safe to flags = defaultFlags; in the object constructor, in order to eliminate the 20 lines for assigning each field individually?

Also, I was wondering what about serialization? According to the compiler, Flags is 4 bytes, can I serialize that as a 32bit unsigned integer and desterilize it as such without any data corruption?

  • 1
    The order for bitfield is unspecified, so for the serialization, you may have issues. – Jarod42 Nov 18 '14 at 16:04
  • 1
    If you really need to save space have you considered using bit masks? – sjdowling Nov 18 '14 at 16:07
  • So if I want it portable I should scrap the bitfield and use manual bit packing instead? –  Nov 18 '14 at 16:08
  • Bitmasks work and give you complete control over ordering and packing. What you've got here is a polite request to the compiler that might be ignored. – tadman Nov 18 '14 at 16:10
  • This is odd, IIRC `struct` members are not subjected to reordering, but this is not the case when struct members are bitfields? –  Nov 18 '14 at 16:13
  • @user3735658 At the very least, endian-ness will affect how the bits get serialized even if the compiler doesn't technically reorder the fields. – Mark B Nov 18 '14 at 16:20
  • The standard says "Allocation of bit-fields within a class object is implementation-defined. Alignment of bit-fields is implementation-defined." However, if you're serializing to and from the same CPU architecture from code compiled on the same compiler version, using the same compiler flags (especially -O flag), then it is safe. – Lasse Reinhold Nov 18 '14 at 16:28
  • @MarkB - endianness is not really a concert, I only target x86/x64 which are little endian and ARM which is bi-endian and IIRC little endian by default. Also, I am using the same compiler for all platforms, does that mean the same implementation is guaranteed across the different platforms? –  Nov 18 '14 at 16:30
  • @user3735658 It's fine to make that argument. I think it's also fair to say that if you can design it to be fully portable for one extra day now then you have portability forever (and may even avert a very long debugging session years in the future). – Mark B Nov 18 '14 at 16:32

1 Answers1

3

My main question is whether it is safe to flags = defaultFlags; in the object constructor, in order to eliminate the 20 lines for assigning each field individually?

Yes. The implicitly defined copy constructor for Flags will assign each Bitfield appropriately. [class.copy]/15:

Each base or non-static data member is copied/moved in the manner appropriate to its type:

  • if the member is an array, [..]
  • if a member m has rvalue reference type T&& [..]
  • otherwise, the base or member is direct-initialized with the corresponding
    base or member of x.

can I serialize that as a 32bit unsigned integer and desterilize it as such without any data corruption?

If you write and read the file on the same machine with the same compiled program, yes. The layout might be different on other compilers or architectures though, the standard doesn't impose any fixed requirements in that respect. [class.bit]/1:

Allocation of bit-fields within a class object is implementation-defined. Alignment of bit-fields is implementation-defined. Bit-fields are packed into some addressable allocation unit. [ Note: Bit-fields straddle allocation units on some machines and not on others. Bit-fields are assigned right-to-left on some machines, left-to-right on others. — end note ]

If you write it into a char array of size sizeof Field, write that into a file and extract it from there again, copying it back into a Field object should thus give you the same values. [basic.types]/2 (emphasis mine):

For any object (other than a base-class subobject) of trivially copyable type T, whether or not the object holds a valid value of type T, the underlying bytes (1.7) making up the object can be copied into an array of char or unsigned char. If the content of the array of char or unsigned char is copied back into the object, the object shall subsequently hold its original value.

However, as pointed out in the comments, full portability (and reasonable efficiency) can be achieved using bitmasks.

Columbo
  • 60,038
  • 8
  • 155
  • 203