Search code examples
c++c++11vectorflexible-array-member

Is it possible to have a std::vector of struct with a fexible array member?


I have a struct with a flexible array member that I need to use.

struct Record
{
     uint32_t length;
     Data contents[];
};

I'm able to initialize this and use it by doing something like this: (it would also work with malloc or any other dynamic allocation)

vector<Data> members;
vector<uint8_t> buffer;
Record myRecord;
buffer.resize(sizeof(Record) + members.size() * sizeof(Data));
myRecord = *(reinterpret_cast<Record*>(buffer.data());
myRecord.length = static_cast<uint32_t>(members.size());
// copy members to myRecord.contents

That works just fine. But now I need to have an interface that operates on batches of Record, and I have been trying to use an std::vector for this. Then problems start appearing, and I'm guessing it's because std::vector arranges all elements contiguously on memory, and since sizeof(Record) won't take into account the size of the contents (each vector element will hold only 4 bytes, instead of 4 bytes + size_of_contents * sizeof(Data)), the vector elements are actually sharing memory and then each element starts overwriting the contents of the previous element. Does that make sense?

If this really is the problem, I was wondering if there's any way to "force" the vector to allocate a specific size for each element (instead of whatever sizeof returns for the element's type). That way I could make sure that each vector element would have enough size. If that's not possible, is there an alternative solution? Maybe a different container that would allow me to do so? Please keep in mind that I do need to use the struct as it's defined (I would love to just replace the whole thing for a vector but unfortunately that's not possible)


Solution

  • Your principle problem is this:

    myRecord = *(reinterpret_cast<Record*>(buffer.data());
    

    That's simply overwriting the data in a stack variable. That does not change the address of myRecord to suddenly point to buffer.data(). Which means when you later do myRecord.contents[...] = ..., you're going to be trashing the stack.

    What you almost certainly intended was:

    Record *myRecord = (reinterpret_cast<Record*>(buffer.data());
    

    Then you would have a pointer to memory managed by buffer, which would have sufficient storage for the myRecord->contents array.

    You cannot treat Record like a value type. As far as C++'s object model is concerned, it's not a value type. It cannot be copied or moved like most C++ types. You can only manipulate it through a pointer/reference to the specific allocation you use here.

    That being said, using a vector to manage the storage for your Record* like this is really weird. It'd be better to use a unique_ptr, since resizing the allocation would be a really bad idea.

    std::unique_ptr<char[]> storage = new char[sizeof(Record) + (members.size() * sizeof(Data))];
    

    This also prevents the system from initializing the memory, since you're going to overwrite it anyway.

    I was wondering if there's any way to "force" the vector to allocate a specific size for each element (instead of whatever sizeof returns for the element's type).

    No. vector manages a contiguous array of elements of the same type. And in C++, all objects of the same type have the same size.