6

Is the following valid C++? It's an alternative way of implementing a variable length tail to a flat structure. In C this is commonly done with the struct hack

struct Str
{
    Str(int c) : count(c) {}
    size_t count;
    Elem* data() { return (Elem*)(this + 1); }
};

Str* str = (Str*)new char[sizeof(Str) + sizeof(Elem) * count];
new (str) Str(count);
for (int i = 0; i < count; ++i)
    new (str->data() + i) Elem();
str->data()[0] = elem0;
str->data()[1] = elem1;
// etc...

I ask this in response to the following related question

Community
  • 1
  • 1
john
  • 85,011
  • 4
  • 57
  • 81
  • 1
    I think this will not work if `Elem` has different alignment than `Str`. – Henrik Nov 26 '13 at 10:51
  • 1
    That's easily fixed: `union { size_t count, Elem dummy; }` – MSalters Nov 26 '13 at 12:15
  • If you want to be really fancy, use template metaprogramming to make `dummy` the smaller of `Elem` and `std::max_align_t`. – MSalters Nov 26 '13 at 12:29
  • It might work. The question is why you would do this kind of thing in C++. There are better ways to write a dynamic homogeneous pointer container. – rubenvb Nov 26 '13 at 12:40

2 Answers2

2

No, it is not valid:

Elem might have different alignment than Str, so (reinterpret_)casting Str+1 to Elem* might or might not give you a valid pointer, and acccessing might give undefined behavior.

But after all, why would you want to do something like that?

Arne Mertz
  • 24,171
  • 3
  • 51
  • 90
  • Why? Low level hackery. But it seems alignment an issue, thanks. – john Nov 26 '13 at 10:56
  • The usual reason is locality of reference. See also `std::make_shared`. Also, IIRC `new char[]` is suitably aligned for all types (to be able to function as a substitute for `malloc`) – MSalters Nov 26 '13 at 12:11
  • @MSalters: `new char[]` is suitably aligned, but after an offset of `Str` all bets are off. – Matthieu M. Nov 26 '13 at 12:20
  • @MatthieuM. Indeed, but my remark was about the first bullet. That's the one claiming `new[]` doesn't return suitable aligned memory. The problem is with the trailing array. (And as I commented, that alignment is fixable) – MSalters Nov 26 '13 at 12:27
  • @MSalters: well actually, `new char[]` might not be aligned correctly for all types. `new char[]` is only guaranteed to be correctly aligned for all standard types, that is up to `alignof(std::max_align_t)`. Whilst this supersedes all types that may be created as a combination of those, there are some specific *vector* types used for SSE/AVX instructions for example that may have even stricter alignment requirements (specified through compiler intrinsics) and those are not covered by this guarantee. Of course, in this very case, it should be suitable for `Str`. – Matthieu M. Nov 26 '13 at 12:32
  • I removed the bullet about `new char[]` – Arne Mertz Nov 26 '13 at 12:37
0

Valid in what sense? It is C++ using C-like techniques which imho is fine as long as the project requirements leave no other choice. If you are asking if it will work, it will as long as data alignment issues do not crash the code (i.e. non x86 like SPARC, etc). C++ behaves much like C when addressing memory.

I tested it using the following modifications under gcc and VS and it works:

struct Elem
{
Elem() : x(0), t(0) { memset(c, 0, sizeof(c));} 
Elem(int v) : x(v), t(0) { memset(c, 0, sizeof(c));} 
Elem(const Elem &e) { *this = e; }

Elem &operator=(const Elem &e)
{
    if (this != &e)
    {
         memcpy(c, e.c, sizeof(c));
         x = e.x;
         t = e.t;
    }
    return *this;
}

char c[21];
int x;
char t;
};

struct Str
{
    Str(int c) : count(c) {}
    size_t count;
    Elem* data() { return (Elem*)(this + 1); }
};

int count = 11;

Str *str = (Str*)new char[sizeof(Str) + sizeof(Elem) * count];
new (str) Str(count);
for (int i = 0; i < count; ++i)
{
    new (str->data() + i) Elem();
    str->data()[i] = Elem(i+1);
}

for (int i=0; i<str->count; i++)
    cout << "[" << i << "]: " << str->data()[i].x << endl;

Also, I added various different size members to Str and Elem to force different padding and played with alignments (VS/some GCC: #pragma pack(...), GCC: __ attribute__ ((aligned (...))) and , __ attribute__(packed) ).

Please note that playing with alignments is not safe on all architectures - Relevant question

Community
  • 1
  • 1
DNT
  • 2,356
  • 14
  • 16