Search code examples
c++serializationenumsunionundefined-behavior

C++ casting int into enum then read that enum as bit field safe or not?


#include <iostream>
#include <stdio.h>
#include <string>
#include <cstring>
using namespace std;

enum class OrderFlags : uint16_t {
  None = 0,
  BuySide = 1,
  UpdateVolume = 2,
  UpdatePrice = 4,
  Market = 8,
  Float = 16,
  Aggressive = 32,
};

struct OrderFlagsBitField {
  uint8_t side : 1;
  uint8_t update_volume : 1;
  uint8_t update_price : 1;
  uint8_t market_order : 1;
  uint8_t float_order : 1;
  uint8_t aggressive : 1;
  uint16_t reserved : 10;
};
static_assert(sizeof(OrderFlagsBitField) == 2);

struct Order {
    int order_id;
    union {
        OrderFlags flags;
        OrderFlagsBitField flags_bf;
    };
};

int main()
{
    Order order1;
    order1.order_id = 123;
    uint16_t value = 1 + 2 + 8 + 32;
    memcpy(&order1.flags, &value, sizeof(value));

    Order order2 = order1;
    cout << int(order2.flags_bf.side) << " " << int(order2.flags_bf.update_volume) << " " << int(order2.flags_bf.update_price) << " "
         << int(order2.flags_bf.market_order) << " " << int(order2.flags_bf.float_order) << " " << int(order2.flags_bf.aggressive) << "\n";
    return 0;
}

I have a code like above. value represents a flag that can contain multiple OrderFlags value. Basically, I want to serialize a uint16_t into a Order object with this union.

Is this code safe or not? For this case, the output should be 1 1 0 1 0 1, even if I write order1 object into a file, then read-as-byte from that file again into order2.


Solution

  • It's undefined behavior. Handle each flag in the bit field instead. Such as:

    int bf2int(BitFieldFlag flag)
    {
      int res = 0;
      if (flag.attribute1) res |= 1 << 0;
      if (flag.attribute2) res |= 1 << 1;
      // ...
      return res;
    }
    
    void int2bf(BitFieldFlag &flag, int flag_int)
    {
      memset(&flag, 0, sizeof(flag));
      if ((flag_int >> 0) & 1) flag.attribute1 = 1;
      if ((flag_int >> 1) & 1) flag.attribute2 = 1;
      // ...
    }