PDA

View Full Version : c++, placement delete upon exception



stinos
21st October 2006, 15:34
Hi All!

I'm having this really nasty problem regarding placement new and delete; more specific: with cleaning up the memory when an exception is thrown in the constructor of a class.

Here's a test class declaring it's own placement new and delete versions:


class memtest
{
public:
//non-placement overloads omitted
static void* operator new( size_t, void* p )
{ return p; }
static void operator delete( void* p, void* )
{ ( (memtest*) p )->~memtest(); free( p ); }
static void* operator new [] ( size_t, void* p )
{ return p; }
static void operator delete [] ( void* p, void* )
{ /* and now ? */ }

memtest() :
a( new int )
{
throw( 0 );
}

~memtest()
{
delete a;
}
};


Now, working with single objects is no big problem, as the compiler generates code to call the corresponding placement delete when the exception is thrown:



int main()
{
try
{
memtest* pMem = (memtest*) malloc( sizeof( memtest ) ); //allocate mem
new( pMem ) memtest; //calls placement new and new handler
delete pMem; //won't be reached due to constructor failure
}
catch(...)
{
//no mem leak, compiler generated code called memtest::operator delete( void* p, void* )
}
}


The problem arises when using placement new [] and delete []: the compiler will also correctly generate code to call operator delete [] ( void*, void* ), but what am I supposed to put in there?



int main()
{
try
{
memtest* pMem = (memtest*) malloc( sizeof( memtest ) * 2 + sizeof( size_t ) ); //allocate mem
pMem = new( pMem ) memtest[ 2 ]; //calls placement new[] and iterates it with new handler
delete [] pMem; //won't be reached
}
catch(...)
{
//memleak or crash ??
}
}


If I would use



operator delete [] ( void* p, void* )
{
free( p );
}


it will free the allocated memory, but memtest's destructor won't get called, resulting in memtest::a leaking.

If I'd put something like this:



operator delete [] ( void* p, void* )
{
size_t* pSecretSize = ((size_t*) p) - 1; //new [] stores size in front of array
for( size_t i = 0 ; i < *pSecretSize ; ++i )
(&(mem)[i])->~memtest(); //call destructor
free( pSecretSize ) ; //free
}


then it will call the destructor for all elements in the array, and afterwards nicely free the memory.
However, when this gets called from within the constructor, there are two problems:
- at first, p will still point to the beginning of the array memory, not to the beginning of the array, so the pSecretSize stuff is completely wrong
- second, even if the pointer passed pointed to the beginning of the array, the code crashes the program since it calls destructors of objects that are not constructed.

Am I missing something, or is it just impossible to not have a leak when using the vector new and delete ?

jpn
22nd October 2006, 13:02
- Why are you mixing malloc/free and new/delete?
- You should never explicitly call the destructor of a class..
- Is there any specific reason for writing your own complex new/delete operators?

Am I missing something here? Why don't you simply cleanup the members before throwing an exception? Or use auto/smart pointer or reference counting techniques.

How should I handle resources if my constructors may throw exceptions? (http://www.parashift.com/c++-faq-lite/exceptions.html#faq-17.4)

It should be as simple as this to handle the cleanup upon exception:


Something* some = 0;
try {
some = new Something[n];
}
catch (...)
{
delete[] some;
some = 0;
}

jacek
22nd October 2006, 19:22
it will free the allocated memory, but memtest's destructor won't get called, resulting in memtest::a leaking.
Are you sure?


#include <cstddef>
#include <iostream>

class Test
{
public:
Test()
{
static int next_id = 0;
id = ++next_id;
std::cerr << __PRETTY_FUNCTION__ << '(' << id << ')' << std::endl;
if( id == 4 ) throw 1;
}

~Test()
{
std::cerr << __PRETTY_FUNCTION__ << '(' << id << ')' << std::endl;
}

static void * operator new[]( size_t, Test *pool )
{
std::cerr << __PRETTY_FUNCTION__ << std::endl;
return pool;
}

static void operator delete []( void *, Test * )
{
std::cerr << __PRETTY_FUNCTION__ << std::endl;
}

private:
int id;
};

int main()
{
size_t count = 5;
char buffer[ sizeof( size_t ) + count * sizeof( Test ) ];

try {
Test *t = new( (Test*)buffer ) Test[ count ];
}
catch(...)
{
}

std::cerr << "done" << std::endl;

return 0;
}

Output:

$ ./a.out
static void* Test::operator new [](unsigned int, Test*)
Test::Test()(1)
Test::Test()(2)
Test::Test()(3)
Test::Test()(4)
Test::~Test()(3)
Test::~Test()(2)
Test::~Test()(1)
static void Test::operator delete [](void*, Test*)
done
It looks like it behaves just like ordinary new[] (note that the destructor won't be called for the object that threw the exception, as that object isn't fully constructed).

You can find some interesting information about placement new here: http://www.parashift.com/c++-faq-lite/dtors.html#faq-11.14 and of course in Lippman's C++ Primer.

stinos
24th October 2006, 13:44
fisrt, thanks for the replies. Jacek, your example is quite interesting.
second, as I reread my post now I slapped myself in the face a few times, I was obviously not thinking clearly at the time of writing it, it's complete bollocks (very likely due to coding 18 hours in row combined with heavy drugs I have to take due to a recent accident).



- Why are you mixing malloc/free and new/delete?

for the reason above I fear



- You should never explicitly call the destructor of a class..

possibly, but it's the only way to destruct something when using placement new, I think.



- Is there any specific reason for writing your own complex new/delete operators?


there are two: first, I'm quite addicted to learning all details, writing the operators myself learned me a lot about new/delete, before I didn't even know you can actually have an unlimited number of overloads of them, next to the standard eight. After this post everything became quite clear..
second, I'm using custom allocators all over the place so I have to overwrite them in order to let them use myallocator::malloc etc

btw I found http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dndeepc/html/deep080599.asp to be very interesting and in-depth too.

stinos
24th October 2006, 17:57
Jacek, just tested your code and it works as expected indeed; however, it didn't compile at first:
- line 38 generates "error: expression must have a constant value" with cl (microsoft) and cl6x (texas instruments); I didn't know it was ok for gcc?
- __PRETTY_FUNCTION__ is g++ specific

anyway, thanks again!

Methedrine
29th October 2006, 22:01
- line 38 generates "error: expression must have a constant value" with cl (microsoft) and cl6x (texas instruments); I didn't know it was ok for gcc?



size_t count = 5;
char buffer[ sizeof( size_t ) + count * sizeof( Test ) ];


That looks like a very constant expression though. Naughty ms/ti compilers.

*edit* ah no, count ain't const.

3dch
31st October 2006, 16:38
The discussion about mixing new and malloc reminded me of when we recently had to port an existing application to a new platform with different compilers than used before. Building the app wasn't a problem, but running it was a nightmare because it crashed randomly. So after digging deep into the code, which was written in different languages and spread over several libraries, we found out that the problem indeed was a new/malloc mix. More details from a C++ expert with name Stroustrup can be found on his website:

http://www.research.att.com/~bs/bs_faq2.html#realloc