The struct hack where you have an array of length 0 as the last member of a struct from C90 and C99 is well known, and with the introduction of flexible array members in C99, we even got a standardized way of using it with []
. Unfortunately, C++ provides no such construct, and (at least with Clang 3.4), compiling a struct with either [0]
or []
will yield a compilation warning with --std=c++11 -pedantic
:
$ cat test.cpp struct hack { char filler; int things[0]; }; $ clang++ --std=c++11 -pedantic test.cpp \test.cpp:3:14: warning: zero size arrays are an extension [-Wzero-length-array] int things[0];
and similarly
$ cat test.cpp struct fam { char filler; int things[]; }; $ clang++ --std=c++11 -pedantic test.cpp \test.cpp:3:7: warning: flexible array members are a C99 feature [-Wc99-extensions] int things[];
My question then is this; say that I want to have a struct that contains an array of variable size as the last item in C++. What is the right thing to do given a compiler that supports both? Should I go with the struct hack [0]
(which is a compiler extension), or the FAM []
(which is a C99 feature)? As far as I understand it, either will work, but I am trying to figure out which is the lesser evil?
Also, before people start suggesting keeping an int*
to a separately allocated piece of memory in the struct instead, that is not a satisfactory answer. I want to allocate a single piece of memory to hold both my struct and the array elements. Using a std::vector also falls into the same category. If you wonder why I don't want to use a pointer instead, the R.'s answer to another question gives a good overview.
There have been some similar questions elsewhere, but none give an answer to this particular question:
e.things[42]
.You can get more or less the same effect using a member function and a reinterpret_cast
:
int* buffer() { return reinterpret_cast<int*>(this + 1); }
This has one major defect: it doesn't guarantee correct alignment. For example, something like:
struct Hack { char size; int* buffer() { return reinterpret_cast<int*>(this + 1); } };
is likely to return a mis-aligned pointer. You can work around this by putting the data in the struct in a union with the type whose pointer you are returning. If you have C++11, you can declare:
struct alignas(alignof(int)) Hack { char size; int* buffer() { return reinterpret_cast<int*>(this + 1); } };
(I think. I've never actually tried this, and I could have some details of the syntax wrong.)
This idiom has a second important defect: it does nothing to ensure that the size field corresponds to the actual size of the buffer, and worse, there is no real way of using new
here. To correct this, somewhat, you can define a class specific operator new
and operator delete
:
struct alignas(alignof(int)) Hack { void* operator new( size_t, size_t n ); void operator delete( void* ); Hack( size_t n ); char size; int* buffer() { return reinterpret_cast<int*>(this + 1); } };
The client code will then have to use placement new to allocate:
Hack* hack = new (20) Hack(20);
The client still has to repeat the size, but he cannot ignore it.
There are also techniques which can be used to prevent creating instances which aren't allocated dynamically, etc., to end up with something like:
struct alignas(alignof(int)) Hack { private: void operator delete( void* p ) { ::operator delete( p ); } // ban all but dynamic lifetime (and also inheritance, member, etc.) ~Hack() = default; // ban arrays void* operator new[]( size_t ) = delete; void operator delete[]( void* p ) = delete; public: Hack( size_t n ); void* operator new( size_t, size_t n ) { return ::operator new( sizeof(Hack) + n * sizeof(int) ); } char size; // Since dtor is private, we need this. void deleteMe() { delete this; } int* buffer() { return reinterpret_cast<int*>(this + 1); } };
Given the fundamental dangers of such a class, it is debatable if so many protective measures are necessary. Even with them, it's really only usable by someone who fully understands all of the constraints, and is carefully paying attention. In all but extreme cases, in very low level code, you'd just make the buffer a std::vector<int>
and be done with it. In all but the lowest level code, the difference in performance would not be worth the risk and effort.
EDIT:
As a point of example, g++'s implementation of std::basic_string
uses something very similar to the above, with a struct
containing a reference count, the current size and the current capacity (three size_t
), followed directly by the character buffer. And since it was written long before C++11 and alignas
/alignof
, something like std::basic_string<double>
will crash on some systems (e.g. a Sparc). (While technically a bug, most people do not consider this a critical problem.)
This is C++, so templates are available:
template <int N> struct hack { int filler; int thing [N]; };
Casting between different pointers to different instantiations will be the difficult issue, then.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With