Placement new[]


J

Joe The Smoe

Hi,

to manage memory allocation for a multi-threaded application and reduce
the use of mutex, each thread is given an object of a class named "pool"
which allocates, recycles and eventually releases a set of memory
blocks (the classical memory pool).

The different objects allocated by each thread can now be allocated
using the given pool object thanks to a placement new and new[]
operators given to each concerned class.

The problem I meet is that I have something working pretty well except
for the placement new[] which places the first element of the array 4
bytes after the address returned by the new[] operator. This causes me
some problem at the time the array has to be released because the memory
address of the array (the address of the first element) does not match
the address given by the pool to the placement new[] operator (for the
pool this is not a memory block that it has allocated).

OK, I could shift the array address by four bytes and it would work but,
I'd rather like to understand.

Thanks for any enlightenment! :)

Cheers,
Joe.

P.S: the placement new, new[], delete, and delete[] operators are
textually identical for each class, I can use a #define macro to add
them to each class. But, is there a better C++ way of doing that?
 
Ad

Advertisements

W

Wouter van Ooijen

Joe The Smoe schreef op 30-Jan-14 9:18 AM:
The problem I meet is that I have something working pretty well except
for the placement new[] which places the first element of the array 4
bytes after the address returned by the new[] operator.

Ordinary new and delete deal with a pointer to a fixed type, hence known
size of the object (the compiler can supply the size to the delete call).

Not so for the delete[] operator: it gets a pointer to the first entry,
but there can be just that one entry, but it could equally be a
1000-entry array. There is no guaranteed way for the compiler to known
this at the delete. Hence a common implementation of new[] puts the size
in front of the allocated block of memory, so delete[] can find it and
knows the size of the block.
P.S: the placement new, new[], delete, and delete[] operators are
textually identical for each class, I can use a #define macro to add
them to each class. But, is there a better C++ way of doing that?

Inherit from a base class?

Wouter
 
T

Thomas Richter

Am 30.01.2014 09:18, schrieb Joe The Smoe:
to manage memory allocation for a multi-threaded application and reduce
the use of mutex, each thread is given an object of a class named "pool"
which allocates, recycles and eventually releases a set of memory
blocks (the classical memory pool).

The different objects allocated by each thread can now be allocated
using the given pool object thanks to a placement new and new[]
operators given to each concerned class.

The problem I meet is that I have something working pretty well except
for the placement new[] which places the first element of the array 4
bytes after the address returned by the new[] operator.

This is a quite typical strategy, namely to be able to record the number
of elements the corresponding delete[] must destroy.

This causes me
some problem at the time the array has to be released because the memory
address of the array (the address of the first element) does not match
the address given by the pool to the placement new[] operator (for the
pool this is not a memory block that it has allocated).

How, actually, are you releasing the memory? If you delete the objects
by delete[], as you should (and not delete, without the brackets), you
should get the right memory address, adjusted with the offset. If this
is not the case, it is possibly a compiler bug (g++ does *not* show this
bug).

Note well you need to provide four operator deletes:

operator delete(void *);
deletes objects. You need to find your pool from the object. This delete
is used for regular delete in the program flow, even if deleting objects
from the pool.

operator delete[](void *);
Used for regular array destruction.

operator delete(void *,Pool);
used if an operator-new fails in the construction of an object in the
initializer list

operator delete[](void *,Pool);
the same for arrays.

Note specifically that your regular delete operator does *not* get the
pool passed in, even if it needs it. It's up to your new()
implementation to store it in an appropriate place if required. Plus,
"placement new" does not match "placement delete" as one may think.
"placement new" is complemented by "regular delete" plus "placement delete".

Greetings,
Thomas
 
J

Joe The Smoe

Le 30/01/2014 09:38, Wouter van Ooijen a écrit :
Joe The Smoe schreef op 30-Jan-14 9:18 AM:
The problem I meet is that I have something working pretty well except
for the placement new[] which places the first element of the array 4
bytes after the address returned by the new[] operator.

Ordinary new and delete deal with a pointer to a fixed type, hence known
size of the object (the compiler can supply the size to the delete call).

Not so for the delete[] operator: it gets a pointer to the first entry,
but there can be just that one entry, but it could equally be a
1000-entry array. There is no guaranteed way for the compiler to known
this at the delete. Hence a common implementation of new[] puts the size
in front of the allocated block of memory, so delete[] can find it and
knows the size of the block.

OK, I understand.

So, my implementation of the class pool is probably wrong. I intended to
put a pointer to the allocating pool object just before the allocated
block asked by a placement new/new[] in order to allow only code
modification by replacing the new operator by a it placement's
counterpart, keeping the delete invocation equal:

// adding a thread local pool object
pool p();

// using placement new in place of usual new operator
toto *ptr = new (p) toto(3);

... (doing something with ptr)

// but object get deleted normally (which that's the point is
// not the common way of doing, I admit...)
delete ptr;

where the delete operator for the class toto is taken from its parent
class "on_pool" --- thanks for the hint :) :

class on_pool
{
public:
// get_pool() returns the address of the allocating pool
// for "this" by fetching the field placed before
// the object's address: "this - sizeof(pool *)" bytes
pool & get_pool() const
{ return **((pool **)(pool::shift_left((void *)this))); };

// as expected the placement new operator
void *operator new(size_t n_byte, pool & p)
{ return p.alloc(n_byte); };

// and its associated "on-exception" delete operator
void operator delete(void *ptr, pool & p)
{ p.dealloc(ptr); };

// the delete operator overwritten to release address
// for the associated pool
void operator delete(void *ptr)
{ ((on_pool *)ptr)->get_pool().dealloc(ptr); };

// this works fine, but doing the same for placement new[]
// and the on_pool::delete[] operators fails because
// delete[] should not be used against object allocated
// by a placement new[] ... I'm hitting compiler internal's
// implementation of new[] and delete[] default operators.
};


class toto: public on_pool
{
...
};


Sight ... :-(

I wished I could abstract well the use of placement new in the current
code, but it seems I cannot avoid directly calling the destructor and
then the delete[] operator. Which to my humble point of view brings a
big difference about the readability of the code and which is source of
error (delete without prior desctructor call, and viceversa),

All the block recycling is done by the pool class not
by the caller (allocation/release abstraction) but I cannot abstract for
the caller the fact that the memory is allocated on the pool by just the
slightly modifying the "new" invocation...

*Sight*

P.S: the placement new, new[], delete, and delete[] operators are
textually identical for each class, I can use a #define macro to add
them to each class. But, is there a better C++ way of doing that?

Inherit from a base class?

Yes, I stupidly thought the new and delete operator where not
inheritable... Just tested it and it perfectly works! Thanks. :)
 
T

Thomas Richter

Am 30.01.2014 14:03, schrieb Joe The Smoe:
OK, I understand.

So, my implementation of the class pool is probably wrong. I intended to
put a pointer to the allocating pool object just before the allocated
block asked by a placement new/new[] in order to allow only code
modification by replacing the new operator by a it placement's
counterpart, keeping the delete invocation equal:

....

Couple of problems with this code, namely the alignment might not be
right. But if operator new[] and operator delete[] are implemented in
the same way, then *that* part should be correct. I'm a bit too lazy to
try your code with g++, but I'll just share mine if you want to take it.

Here's an implementation that works with g++: What I show here is the
class you need to inherit from to be able to "pool allocate" any
derived classes.

class JObject {
private:
// Make Operator New without arguments private here.
// They must not be used.
void *operator new[](size_t)
{
assert(false);
return (void *)1; // GNU doesn't accept NULL here. This should
throw as well.
}
void *operator new(size_t)
{
assert(false);
return (void *)1;
}
//
// Private structure for effective arena-new management:
//
// Due to bugs in both the GCC and the VS, we must keep the
// size in the memory holder as well. The size parameter of
// the delete operator is just broken (a little broken on GCC,
// a lot broken on VS).
// This is still slightly more effective than using AllocVec
// here.
// *BIG SIGH*
union MemoryHolder {
// This union here enforces alignment
// of the data.
struct {
// back-pointer to the environment
class Environ *mh_pEnviron;
size_t mh_ulSize;
} mh;
// The following are here strictly for alignment
// reasons.
union Environ::Align mh_Align;
};
//
public:
// An overloaded "new" operator to allocate memory from the
// environment.
static void *operator new[](size_t size,class Environ *env)
{
union MemoryHolder *mem;
// Keep the memory holder here as well
size += sizeof(union MemoryHolder);
// Now allocate the memory from the environment.
mem = (union MemoryHolder *)env->AllocMem(size);
// Install the environment pointer and (possibly) the size
mem->mh.mh_pEnviron = env;
mem->mh.mh_ulSize = size;
return (void *)(mem + 1);
}
//
static void *operator new(size_t size,class Environ *env)
{
union MemoryHolder *mem;
// Keep the memory holder here as well
size += sizeof(union MemoryHolder);
// Now allocate the memory from the environment.
mem = (union MemoryHolder *)env->AllocMem(size);
// Install the environment pointer and (possibly) the size
mem->mh.mh_pEnviron = env;
mem->mh.mh_ulSize = size;
return (void *)(mem + 1);
}
//
//
// An overloaded "delete" operator to remove memory from the
// environment.
static void operator delete[](void *obj)
{
if (obj) {
union MemoryHolder *mem = ((union MemoryHolder *)(obj)) - 1;
mem->mh.mh_pEnviron->FreeMem(mem,mem->mh.mh_ulSize);
}
}
//
//
static void operator delete(void *obj)
{
if (obj) {
union MemoryHolder *mem = ((union MemoryHolder *)(obj)) - 1;
mem->mh.mh_pEnviron->FreeMem(mem,mem->mh.mh_ulSize);
}
}
};

The "Environment" is what your pool is. It provides functions AllocMem()
and FreeMem() that handle memory. Both require a pointer and a size. It
also contains a small union of the following type:

union Align {
UBYTE a_byte;
UWORD a_word;
ULONG a_int;
FLOAT a_float;
DOUBLE a_double;
UQUAD a_quad;
APTR a_ptr;
};

that is just there to ensure correct alignment of all types. The UPPER
CASE names there are environment specific types and set accordingly,
using autoconf.

The construction works fine with g++, and VS, though a couple of bugs
were present that required me to keep the size in the allocation itself
because the compiler provided value was, for some versions of the
compilers, incorrect.

To use it, first derive from JObject:

class bla : public JObject {
...
}

and then:

foo = new(environ) bla(...);

delete foo;

does what it should do, including arrays (tested). If it does not,
you're mixing new[] with delete, or new with delete[].

HTHH,
Thomas
 
Ad

Advertisements

J

Joe The Smoe

Am 30.01.2014 14:03, schrieb Joe The Smoe:
[...]

Hello Thomas,
Couple of problems with this code, namely the alignment might not be
right. But if operator new[] and operator delete[] are implemented in
the same way, then *that* part should be correct.

after having carefully looked at your implementation --- thank you very
much! :) --- put apart the lack of alignment --- thanks again! --- I did
not understood why your implementation should be working while mine was
not, seen the many similarities... Well, it was evident... I was using
delete in place of delete[] for the array... I should have seen it
before posting... I feel ashamed... However I'm happy having asked as
else I would have missed the alignment consideration :)
I'm a bit too lazy to
try your code with g++, but I'll just share mine if you want to take it.

No need to try it, it works as expected... if you don't mix new[] with
delete and new with delete[]... as I did :)

By the way I was wondering why you forbade the normal new[]() and new()
operators putting them as private:

It could be possible to allow them by setting the value of the pointer
on class Environ in the MemoryHolder data to NULL. Memory allocation
could be then done by operator ::new char[size]. Then, to known how to
delete/delete[] the data it would just require to check whether the
mh_pEnviron field is NULL (use ::delete[]) or not (release calling the
Environ object as actually done), no?
Here's an implementation that works with g++: What I show here is the
class you need to inherit from to be able to "pool allocate" any
derived classes.

[...]


The construction works fine with g++, and VS, though a couple of bugs
were present that required me to keep the size in the allocation itself
because the compiler provided value was, for some versions of the
compilers, incorrect.

Here, I cannot figure out why you need the size to release a memory
block... but that's probably because of the class Environ
implementation, right?

[...]
does what it should do, including arrays (tested). If it does not,
you're mixing new[] with delete, or new with delete[].

Yes, I was.
HTHH,
Thomas

Thanks again,
Joe.
 
Ad

Advertisements


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

Top