Search code examples
c++flatbuffers

Is FlatBuffers C++ reinterpret_cast access actually undefined behavior? Is it practically OK to do that?


Recently I try to use FlatBuffers in C++. I found FlatBuffers seems to use a lot of type punning with things like reinterpret_cast in C++. This make me a little uncomfortable because I've learned it's undefined behavior in many cases.

e.g. Rect in fbs file:

struct Rect {
    left:int;
    top:int;
    right:int;
    bottom:int;
}

turns into this C++ code for reading it from a table:

  const xxxxx::Rect *position() const {
    return GetStruct<const xxxxx::Rect *>(VT_POSITION);
  }

and the definition of GetStruct simply uses reinterpret_cast.

My questions are:

  1. Is this really undefined behavior in C++?
  2. In practice, will this kind of usage actually be problematic?

Update:

The buffer can just came from network or disk. I don't know if it's different if the buffer actually came from same memory written by writer of the same C++ program.

But the writer's auto-generated method is:

  void add_position(const xxxxx::Rect *position) {
    fbb_.AddStruct(Char::VT_POSITION, position);
  }

which will use this method and this method and so use reinterpret_cast also.


Solution

  • I didn't analyze the whole FlatBuffers' source code, but I didn't see where these objects are created: I see no new expression, which would create P objects here:

    template<typename P> P GetStruct(voffset_t field) const {
        auto field_offset = GetOptionalFieldOffset(field);
        auto p = const_cast<uint8_t *>(data_ + field_offset);
        return field_offset ? reinterpret_cast<P>(p) : nullptr;
      }
    

    So, it seems that this code does have undefined behavior.

    However, this is only true for C++17 (or pre). In C++20, there will be implicit-lifetime objects (for example, scalar types, aggregates are implicit-lifetime types). If P has implicit lifetime, then this code can be well defined. Provided that the same memory area are always accessed by a type, which doesn't violate type-punning rules (for example, it always accessed by the same type).