Zero-size array as struct member

G

gwowen

Wrong? C++ does not offer tools to abstract the struct hack and make
it easier and safer to use?

  We'll have to disagree, then.

The struct hack gives me a storage region of run-time-variable size,
contiguous with a place to store that size itself. Which C++
container gives you that? How would you abstract that requirement.

(Clue: Given such a container, what would sizeof(Container) evaluate
to - remember this needs to be a compile time constant?).
 
V

Vladimir Jovic

Helge said:
Vladimir Jovic said:
Helge said:
Helge Kruse wrote:

Well, let's say that the only advantage I can think of with struct
hacks
is efficiency (they consume less memory and are faster to allocate
than
the "normal" solution of allocating the struct and the array
separately).

I can't think of any significant advantages. If memory usage
efficiency
is not an issue, why would I use the struct hack for anything?
Marshalling can be an issue. Think of passing the struct from one
process to another using shared memory. When you have the array in a
stl vector, you have additional costs. The same applies to inter
process communication using any socket layer like TCP.
But in that case, why use hacks of this type? For IPC, you can not pass
pointers. There are other mechanisms, therefore there are no needs for
such hacks.
What pointers? The OP wrote this:

struct SvrList{
unsigned int uNum;
GameSvr svr[0]; //line A
};
You switched to interprocess communication. For that, this hack should not
be used. For IPC (at least on linux, don't know how it is done on other
platforms), it is done this way:
http://linux.die.net/man/7/mq_overview
http://linux.die.net/man/2/mmap
http://linux.die.net/man/2/shmget

Thanks for these (os-dependent) examples. These functions like msgsnd expect
a pointer to _one_ object. That fits perfect to the struct show above. The
GameSvr array is embedded in the SrvList object. This avoids pointers.

Ok, I see. By using the hack, you will send variable size array of
objects. Not bad.

When you have a pointer to an addtional chunk of data, like std::vector
implementations use, instead of an embedded object you have to build an
message object, compose a memory layout and than you are ready to pass it to
msgsend. Finally you drop the temporary object.

For IPC you can not even use vectors. At least I do not see how.
 
J

Juha Nieminen

gwowen said:
The struct hack gives me a storage region of run-time-variable size,
contiguous with a place to store that size itself. Which C++
container gives you that? How would you abstract that requirement.

"C++ offers you tools" does not mean "C++ offers you a standard container".

C++ offers tools for you to build your own abstract containers which
internally use the struct hack, but from the outside are easier and safer
to use than using the raw struct hack directly.

Such tools include classes (with public and private sections),
constructors, copy constructors, assignment operator overloading,
destructors, the RAII mechanism, and templates.

In other words, instead of using a raw struct pointer directly (which
points to a memory block which has been "overallocated" for the purposes
of the struct hack), you use an abstract object which resembles a smart
pointer and which hides and automatizes the low-level dirty details inside,
making the usage of the struct hack easier and safer.
 
G

gwowen

C++ offers tools for you to build your own abstract containers which
internally use the struct hack, but from the outside are easier and safer
to use than using the raw struct hack directly.

Really? I challenge you write one suitable for use in IPC. Remember,
if your abstract container contains a pointer, its not going to work.
 
A

Alf P. Steinbach /Usenet

* gwowen, on 27.08.2010 13:20:
Really? I challenge you write one suitable for use in IPC. Remember,
if your abstract container contains a pointer, its not going to work.

Please post code that shows the raw struct hack in action without using a pointer.



Cheers & hth.,

- Alf
 
J

Juha Nieminen

gwowen said:
Really? I challenge you write one suitable for use in IPC. Remember,
if your abstract container contains a pointer, its not going to work.

What do you think malloc() returns?

That's right: It returns a raw pointer. This pointer points to a
memory block which you have to manage manually. You need to make sure
it doesn't leak, you don't free it more than once, you don't use it
after it has been freed, and you don't access the memory block out of
boundaries. Additionally, there's also the small chance of making a
mistake with the parameter to malloc() as well (in other words, you
accidentally allocate too little, or too much much memory, eg. because
of a typo or a miscalculation).

That's where C++ offers you tools to both make it easier and safer.

Think about char* vs. std::string. Both are used for basically the
same thing, but the latter is much harder to leak, to delete twice, or
to access after it has been deleted (and in theory the latter could have
additional checks to make sure you don't index out of boundaries, which
is the case with some compilers in debug mode).
 
I

Ian Collins

Really? I challenge you write one suitable for use in IPC. Remember,
if your abstract container contains a pointer, its not going to work.

I write a lot of IPC library code and I've never resorted to to the
struct hack in C++. Show us an example where you have had to use it and
I'm sure you'll get plenty of counterexamples.
 
J

joe

Juha said:
"C++ offers you tools" does not mean "C++ offers you a standard
container".

C++ offers tools for you to build your own abstract containers which
internally use the struct hack, but from the outside are easier and
safer to use than using the raw struct hack directly.

Such tools include classes (with public and private sections),
constructors, copy constructors, assignment operator overloading,
destructors, the RAII mechanism, and templates.

In other words, instead of using a raw struct pointer directly (which
points to a memory block which has been "overallocated" for the
purposes
of the struct hack), you use an abstract object which resembles a
smart pointer and which hides and automatizes the low-level dirty
details inside, making the usage of the struct hack easier and safer.

OK. I thought you were saying originally above that C++ offered an
alternative to the struct hack (because you have been on the track that
something with a std::vector in it was the "same thing"). The "same ol"
still applies though: because C++ does not recognize the struct hack as a
viable technique (like C99 does), you're still (and I do, do this)
wrapping a hack that may be compiler-specific or perhaps not guaranteed
to work. And also, it's hackish because in certain forms you loose the
ability to derive from such a wrapper, etc, and is therefore a
dialect-style of C++ programming.
 
J

joe

Alf said:
* gwowen, on 27.08.2010 13:20:

Please post code that shows the raw struct hack in action without
using a pointer.

struct msg
{
uint32 msg_id;
uint32 data_cap; // capacity
uint32 data_len; // length
char data[1];

msg(uint32 id, uint32 cap, uint32 len, const char* dat)
{
msg_id = id;
data_cap = cap;
data_len = 0;
strncpy(data, dat, len);
}
};

void sendmsg(msg const& m);

const uint32 MSG_ID_HELLO = 1;

int main()
{
unsigned char buff[256];
msg* m =
new((void*)buff) msg(MSG_ID_HELLO, 244, 13, "Hello world!");
sendmsg(m);
return 0;
}
 
J

joe

joe said:
Alf said:
* gwowen, on 27.08.2010 13:20:

Please post code that shows the raw struct hack in action without
using a pointer.

struct msg
{
uint32 msg_id;
uint32 data_cap; // capacity
uint32 data_len; // length
char data[1];

msg(uint32 id, uint32 cap, uint32 len, const char* dat)
{
msg_id = id;
data_cap = cap;
data_len = 0;
strncpy(data, dat, len);
}
};

void sendmsg(msg const& m);

const uint32 MSG_ID_HELLO = 1;

int main()
{
unsigned char buff[256];
msg* m =
new((void*)buff) msg(MSG_ID_HELLO, 244, 13, "Hello world!");
sendmsg(m);
return 0;
}

data_len = 0 in the constructor of msg should have been: data_len = len.
(I initially wrote the constructor as a simple initializing one without
the ID, data, and length args).
 
J

Joshua Maurice

OK. I thought you were saying originally above that C++ offered an
alternative to the struct hack (because you have been on the track that
something with a std::vector in it was the "same thing"). The "same ol"
still applies though: because C++ does not recognize the struct hack as a
viable technique (like C99 does), you're still (and I do, do this)
wrapping a hack that may be compiler-specific or perhaps not guaranteed
to work. And also, it's hackish because in certain forms you loose the
ability to derive from such a wrapper, etc, and is therefore a
dialect-style of C++ programming.

I don't think you could ever derive from a struct hack struct in C++.
The data trails off past the end of the struct hack struct, and if you
derive from the struct and add new data members, then those data
members will exist in the "off the end" hack part from the base class.
This makes no sense to me offhand.

There is a way to do the equivalent of the struct hack in a fully
conforming way in C90 and C++03. It requires some slightly different
notation for use, but it has the same performance and layout. What
follows is some mostly untested code of the idea for C++03. I'm not
entirely sure it's correct, and while it does use some creative
casting hackery, I think that it is perfectly conforming code which is
guaranteed by standard to do what is intended, and it should work on
all reasonable implementations as well.

#ifndef SAFE_STRUCT_HACK_HPP_INCLUDE
#define SAFE_STRUCT_HACK_HPP_INCLUDE

#include <cstdlib>
using std::size_t; //because I'm lazy for this sample

// Public Interface

template <typename prefix_t, typename element_t>
struct safe_struct_hack_t; //Type not defined. It's an opaque type.

template <typename prefix_t, typename element_t>
inline
safe_struct_hack_t<prefix_t, element_t>*
safe_struct_hack_allocate(
size_t num_elements);

template <typename prefix_t, typename element_t>
inline
void
deallocate(
safe_struct_hack_t<prefix_t, element_t> const* );

template <typename prefix_t, typename element_t>
inline
prefix_t*
get_prefix(
safe_struct_hack_t<prefix_t, element_t>* );
template <typename prefix_t, typename element_t>
inline
prefix_t const*
get_prefix(
safe_struct_hack_t<prefix_t, element_t> const* );

template <typename prefix_t, typename element_t>
inline
size_t
get_num_elements(
safe_struct_hack_t<prefix_t, element_t>const * );

template <typename prefix_t, typename element_t>
inline
element_t*
get_element(
safe_struct_hack_t<prefix_t, element_t>* , size_t index);
template <typename prefix_t, typename element_t>
inline
element_t const*
get_element(
safe_struct_hack_t<prefix_t, element_t> const* , size_t
index);


//Private Implementation follows.
//Do not use or depend on implementation details!

//Overly conservative, but it should work portably
//for alignment. Right?
template <size_t x, size_t y>
struct roundup_x_to_nearest_multiple_of_y
{ enum { remainder = (x % y) };
enum { result = (remainder == 0) ? (x) : (x + (y - remainder)) };
};
template <typename prefix_t, typename element_t>
struct safe_struct_hack_offset_to_num_elements
{ enum { x = roundup_x_to_nearest_multiple_of_y<sizeof(prefix_t),
sizeof(size_t)>::result };
};
template <typename prefix_t, typename element_t>
struct safe_struct_hack_offset_to_first_element
{ enum { x =
roundup_x_to_nearest_multiple_of_y<
safe_struct_hack_offset_to_num_elements<prefix_t,
element_t>::x + sizeof(size_t),
sizeof(element_t)
>::result
};
};

template <typename prefix_t, typename element_t>
inline
safe_struct_hack_t<prefix_t, element_t>*
safe_struct_hack_allocate(
size_t num_elements)
{ size_t const allocated_size =
safe_struct_hack_offset_to_first_element<prefix_t,
element_t>::x
+ num_elements * sizeof(element_t);
char* c = new char[allocated_size];
safe_struct_hack_t<prefix_t, element_t>* const x =
reinterpret_cast<safe_struct_hack_t<prefix_t,
element_t>*>(c);
try
{ new (c) prefix_t;
try
{ new (c + safe_struct_hack_offset_to_num_elements<prefix_t,
element_t>::x) size_t(num_elements);
size_t i=0;
try
{ for ( ; i < num_elements; ++i)
new (get_element(x, i)) element_t();
} catch (...)
{ for (;;)
{ if (i == 0)
break;
--i;
get_element(x, i) -> ~element_t();
}
throw;
}
} catch (...)
{ reinterpret_cast<prefix_t*>(c) -> ~prefix_t();
throw;
}
} catch (...)
{ delete[] c;
throw;
}
return x;
}

template <typename prefix_t, typename element_t>
inline
void
deallocate(
safe_struct_hack_t<prefix_t, element_t> const* x)
{ reinterpret_cast<prefix_t const*>(x) -> ~prefix_t();
size_t const num_elements = get_num_elements(x);
for (size_t i=0; i<num_elements; ++i)
get_element(x, i) -> ~element_t();
delete[] reinterpret_cast<char const*>(x);
}

template <typename prefix_t, typename element_t>
inline
prefix_t*
get_prefix(
safe_struct_hack_t<prefix_t, element_t>* x)
{ return reinterpret_cast<prefix_t*>(x);
}
template <typename prefix_t, typename element_t>
inline
prefix_t const*
get_prefix(
safe_struct_hack_t<prefix_t, element_t> const* x)
{ return reinterpret_cast<prefix_t const*>(x);
}

template <typename prefix_t, typename element_t>
inline
size_t
get_num_elements(
safe_struct_hack_t<prefix_t, element_t>const * x)
{ char const* c =
reinterpret_cast<char const*>(x)
+ safe_struct_hack_offset_to_num_elements<prefix_t,
element_t>::x;
return * reinterpret_cast<size_t const*>(c);
}

template <typename prefix_t, typename element_t>
inline
element_t*
get_element(
safe_struct_hack_t<prefix_t, element_t>* x, size_t index)
{ char* ptr_to_first_ele =
reinterpret_cast<char*>(x);
+ safe_struct_hack_offset_to_first_element<prefix_t,
element_t>::x;
return reinterpret_cast<element_t*>(ptr_to_first_ele) + index;
}
template <typename prefix_t, typename element_t>
inline
element_t const*
get_element(
safe_struct_hack_t<prefix_t, element_t> const* x, size_t
index)
{ char const* ptr_to_first_ele =
reinterpret_cast<char const*>(x);
+ safe_struct_hack_offset_to_first_element<prefix_t,
element_t>::x;
return reinterpret_cast<element_t const*>(ptr_to_first_ele) +
index;
}

#endif
 
J

joe

Joshua said:
I don't think you could ever derive from a struct hack struct in C++.

That depends on what you consider the struct hack. I use an array with a
dimension of 1 and all is fine.
The data trails off past the end of the struct hack struct, and if you
derive from the struct and add new data members, then those data
members will exist in the "off the end" hack part from the base class.
This makes no sense to me offhand.

It is assumed that a developer smart enough to know the technique of the
struct hack will not be adding data members in derived classes! Added
behavior, OK. Adding data members is a no no. Also assumed, since structs
based on the struct hack are close-to-the-metal, is that virtual
functions won't be used. These things are close to being primitives after
all.

[I'll have to digest your template code later]
 
I

Ian Collins

Alf said:
* gwowen, on 27.08.2010 13:20:

Please post code that shows the raw struct hack in action without
using a pointer.

struct msg
{
uint32 msg_id;
uint32 data_cap; // capacity
uint32 data_len; // length
char data[1];

msg(uint32 id, uint32 cap, uint32 len, const char* dat)
{
msg_id = id;
data_cap = cap;
data_len = 0;
strncpy(data, dat, len);
}
};

void sendmsg(msg const& m);

The obvious design fix is to pass the header and a pointer to the data
to sendmsg and save the unnecessary copy!
 
J

joe

Ian said:
Alf said:
* gwowen, on 27.08.2010 13:20:

C++ offers tools for you to build your own abstract containers
which internally use the struct hack, but from the outside are
easier and safer to use than using the raw struct hack directly.

Really? I challenge you write one suitable for use in IPC.
Remember, if your abstract container contains a pointer, its not
going to work.

Please post code that shows the raw struct hack in action without
using a pointer.

struct msg
{
uint32 msg_id;
uint32 data_cap; // capacity
uint32 data_len; // length
char data[1];

msg(uint32 id, uint32 cap, uint32 len, const char* dat)
{
msg_id = id;
data_cap = cap;
data_len = 0;
strncpy(data, dat, len);
}
};

void sendmsg(msg const& m);

The obvious design fix is to pass the header and a pointer to the data
to sendmsg and save the unnecessary copy!

It was just an example of how the struct hack works, not an actual usage.
 
A

Alf P. Steinbach /Usenet

* Ian Collins, on 28.08.2010 01:02:
Alf said:
* gwowen, on 27.08.2010 13:20:

C++ offers tools for you to build your own abstract containers which
internally use the struct hack, but from the outside are easier and
safer to use than using the raw struct hack directly.

Really? I challenge you write one suitable for use in IPC. Remember,
if your abstract container contains a pointer, its not
going to work.

Please post code that shows the raw struct hack in action without
using a pointer.

struct msg
{
uint32 msg_id;
uint32 data_cap; // capacity
uint32 data_len; // length
char data[1];

msg(uint32 id, uint32 cap, uint32 len, const char* dat)
{
msg_id = id;
data_cap = cap;
data_len = 0;
strncpy(data, dat, len);
}
};

void sendmsg(msg const& m);

The obvious design fix is to pass the header and a pointer to the data to
sendmsg and save the unnecessary copy!

Hm, somehow Joe's reply didn't show up on my server.

So, I don't know what the code above is meant to illustrate (no context).

However, my question was rhetorical: there's no way to allocate an arbitrary
(known at run time) sized struct without using dynamic allocation.


Cheers,

- Alf
 
J

Juha Nieminen

joe said:
Alf said:
Please post code that shows the raw struct hack in action without
using a pointer.

int main()
{
unsigned char buff[256];
msg* m =
new((void*)buff) msg(MSG_ID_HELLO, 244, 13, "Hello world!");
sendmsg(m);
return 0;
}

What do you think that 'm' is if not a pointer?

And your code perfectly exemplifies why that pointer should be abstracted
away in C++. You are leaking it.
 
Ö

Öö Tiib

joe said:
Alf P. Steinbach /Usenet wrote:
Please post code that shows the raw struct hack in action without
using a pointer.
int main()
{
  unsigned char buff[256];
  msg* m =
      new((void*)buff) msg(MSG_ID_HELLO, 244, 13, "Hello world!");
  sendmsg(m);
  return 0;
}
  What do you think that 'm' is if not a pointer?
  And your code perfectly exemplifies why that pointer should be
  abstracted
away in C++. You are leaking it.

Sorry, but there is no leak (cannot be as there is no dynamic memory
allocation). And one can easily get rid of the m pointer, it is not
needed here, one could just sendmsg(buff).

OTOH, this code exhibits UB because buff is not guaranteed to be aligned
properly for msg. But this can be fixed.

It can be fixed yes. Like they say here that the whole point of struct
hack is to have something that has run-time decided size. If it is
created into storage with compile-time constant size (256 bytes)
then ... what was the point again?
 
J

joe

Alf said:
* Ian Collins, on 28.08.2010 01:02:
Alf P. Steinbach /Usenet wrote:
* gwowen, on 27.08.2010 13:20:

C++ offers tools for you to build your own abstract containers
which internally use the struct hack, but from the outside are
easier and safer to use than using the raw struct hack directly.

Really? I challenge you write one suitable for use in IPC.
Remember, if your abstract container contains a pointer, its not
going to work.

Please post code that shows the raw struct hack in action without
using a pointer.

struct msg
{
uint32 msg_id;
uint32 data_cap; // capacity
uint32 data_len; // length
char data[1];

msg(uint32 id, uint32 cap, uint32 len, const char* dat)
{
msg_id = id;
data_cap = cap;
data_len = 0;
strncpy(data, dat, len);
}
};

void sendmsg(msg const& m);

The obvious design fix is to pass the header and a pointer to the
data to sendmsg and save the unnecessary copy!

Hm, somehow Joe's reply didn't show up on my server.

So, I don't know what the code above is meant to illustrate (no
context).

It was in response to your solicitation (as shown above) to post code
that shows the struct hack in action without using a pointer. At least
that's how I groked what you were looking for.
However, my question was rhetorical: there's no way to allocate an
arbitrary (known at run time) sized struct without using dynamic
allocation.

Well now you are talking about VLAs and that is different from "flexible
array member". VLA implementations do the allocation implicitly so just
saying "dynamic allocation" is not enough. There is explicit (by the
programmer) dynamic allocation, implicit (by the compiler) dynamic
allocation and those can be either on the stack (alloca) or on the heap.

The struct hack is, of course, not about VLA but rather "flexible array
member". Note that if, in my example, the dimension of data is changed to
some other number like 10, you have a fixed-size msg but it's
structurally as much a msg still (and can be used as one after a cast) as
the variable-length one:

struct msg10
{
uint32 msg_id;
uint32 data_cap;
uint32 data_len;
char data[10];
};
 
J

joe

Juha said:
joe said:
Alf said:
Please post code that shows the raw struct hack in action without
using a pointer.

int main()
{
unsigned char buff[256];
msg* m =
new((void*)buff) msg(MSG_ID_HELLO, 244, 13, "Hello world!");
sendmsg(m);
return 0;
}

What do you think that 'm' is if not a pointer?

And your code perfectly exemplifies why that pointer should be
abstracted away in C++. You are leaking it.

Nothing is leaking. The "new" is placement new. There are no pointers in
the struct msg as there would be if some other C++-like composition were
used.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top