Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Struct hack equivalent in C++

Tags:

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:

  • Are flexible array members valid in C++?: Very similar, but the question there is whether FAM is valid in C++ (no). I am looking for a good reason to pick one or the other.
  • Conforming variant of the old “struct hack”: Proposes an alternative, but it's neither pretty, nor always correct (what if padding is added to the struct?). Accessing the elements later is also not as clean as doing e.things[42].
like image 408
Jon Gjengset Avatar asked Nov 29 '13 16:11

Jon Gjengset


2 Answers

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.)

like image 125
James Kanze Avatar answered Oct 17 '22 00:10

James Kanze


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.

like image 21
rodrigo Avatar answered Oct 17 '22 00:10

rodrigo