problem 1 plugging the memory leak when you


Problem #1: plugging the memory leak. When you employ the "normal" new operator, for example Foo* p = new Foo(), the compiler generates some special code to manage the case while the constructor throws an exception. The actual code produced by the compiler is functionally similar to this:

// It is functionally what happens with Foo* p = new Foo() Foo* p;

// don't catch exceptions thrown through the allocator itself void* raw = operator new(sizeof(Foo));

// catch any exceptions thrown through the ctor try {

p = new(raw) Foo(); // call the ctor with raw as this

}

catch (...) {

// oops, ctor threw an exception operator delete(raw);

throw; // rethrow the ctor's exception

}

The point is which the compiler deallocates the memory if the ctor throws an exception. However in particular case of the "new with parameter" syntax (called commonly "placement new"), the compiler won't know that what to do if the exception take places so by default it does nothing:

// It is functionally what happens with Foo* p = new(pool) Foo():

void* raw = operator new(sizeof(Foo), pool);

// the above function returns "pool.alloc(sizeof(Foo))" simply

Foo* p = new(raw) Foo();

// if the above line "throws", pool.dealloc(raw) is NOT called

Thus the goal is to force the compiler to do something same to what it does with the global new operator. Luckily it's simple: while the compiler sees new(pool) Foo(), this looks for a corresponding operator delete. If it determines one, it does the equivalent of wrapping the ctor call in a try block as illustrated above. Thus we would provide simply an operator delete with the following signature (be careful to obtain this right; if the second parameter has a distinct type from the second parameter of the operator new(size_t, Pool&), the compiler doesn't protest; it bypasses the try block simply when your users say new(pool) Foo()):

void operator delete(void* p, Pool& pool)

{

pool.dealloc(p);

}

After this, the compiler will wrap the ctor calls of your new expressions automatically in a try block:

// This is functionally what happens along with Foo* p = new(pool) Foo() Foo* p;

// don't catch exceptions thrown through the allocator itself void* raw = operator new(sizeof(Foo), pool);

// the above returns "pool.alloc(sizeof(Foo))" simply

// catch any exceptions thrown through the ctor try {

p = new(raw) Foo(); // call the ctor along with raw as this

}

catch (...) {

// oops, ctor threw an exception

operator delete(raw, pool); // that's the magical line!!

throw; // rethrow the ctor's exception

}

In other words, the one-liner function operator delete(void* p, Pool& pool) causes the compiler to automagically plug the memory leak. Certainly that function can be, but doesn't need to be, inline.

Problems #2 ("ugly thus error prone") and #3 ("users have to manually associate pool-pointers along with the object that allocated them, that is error prone") are simultaneously solved along an additional 10-20 lines of code in one place. In other terms, we add 10-20 lines of code in one place (your Pool header file) and make simpler an arbitrarily large number of other places (each piece of code which uses your Pool class).

The idea is to implicitly linked a Pool* with every allocation. The Pool* linked with the global allocator would be NULL, however at least conceptually you could say each allocation has linked Pool*. Then you replace the global operator delete thus it looks up the linked Pool*, and if non-NULL, calls that Pool's deallocate function. For instance, if(!) the normal deallocator utilized free(), the replacment for the global operator delete would look something as:

void operator delete(void* p)

{

if (p != NULL) {

Pool* pool = /* somehow get the associated 'Pool*' */;

if (pool == null)

free(p);

else

pool->dealloc(p);

}

}

If you're not definite if the normal deallocator was free(), the simplest approach is also replace the global operator new with something which uses malloc(). The replacement in support of the global operator new would look like this (note: this definition avoid a few details like the new_handler loop and the throw std::bad_alloc() which happens if we run out of memory):

void* operator new(size_t nbytes)

{

if (nbytes == 0)

nbytes = 1; // thus all alloc's get a distinct address void* raw = malloc(nbytes);

...somehow associate the NULL 'Pool*' with 'raw'... return raw;

}

The only remaining difficulty is to linked a Pool* with an allocation. One approach, utilized in at least one commercial product, is to employ a std::map. In other terms, build a look-up table whose keys are allocation-pointer and whose values are the linked Pool*. It is necessary that you insert a key/value pair into the map only in operator new(size_t,Pool&). Particularly, you ought to not insert a key/value pair from the global operator new (for example you have to not say, poolMap[p] = NULL in the global operator new).

Reason: doing that would develop a nasty chicken-and-egg difficulty since std::map probably employ the global operator new, it ends up inserting a new entry each time inserts a new entry, leading to infinite recursion bang you're dead.

Although this technique needs a std::map look-up for each deallocation, it appears to have acceptable performance, at least in several cases.

Another approach which is faster however might use more memory and is a little trickier is to prepend a Pool* just before all of allocations. For instance, if n bytes was twenty four, meaning the caller was asking to allocate 24 bytes, we would allocate 28 (or 32 if the machine need 8-byte alignment for things as doubles and/or long longs), stuff the Pool* to the first four bytes, and return the pointer 4 (or 8) bytes from the starting of what you allocated. After that your global operator delete backs off the 4 (or 8) bytes, determine the Pool*, and if NULL, uses free() or else calls pool- >dealloc(). The parameter passed to free () and pool->dealloc() would be the pointer 4 (or 8) bytes to left of the original parameter, p. If (!) you decide on four byte alignment, your code would look like this (although as before, the following operator new code elides common out-of-memory handlers):

void* operator new(size_t nbytes)

{

if (nbytes == 0)

nbytes = 1; // so all alloc's get a distinct address

void* ans = malloc(nbytes + 4); // overallocate by 4 bytes

*(Pool**)ans = NULL; // use NULL in the global new return (char*)ans + 4; // don't let users see the Pool*

}

void* operator new(size_t nbytes, Pool& pool)

{

if (nbytes == 0)

nbytes = 1; // thus all alloc's get a distinct address

void* ans = pool.alloc(nbytes + 4); // overallocate through 4 bytes

*(Pool**)ans = &pool; // put the Pool* here

return (char*)ans + 4; // don't allow users see the Pool*

}

void operator delete(void* p)

{

if (p != NULL) {

p = (char*)p - 4; // back off to the Pool* Pool* pool = *(Pool**)p;

if (pool == null)

free(p); // note: 4 bytes left of the original p else

pool->dealloc(p); // note: four bytes left of the original p

}

}

Of course the last few paragraphs of this FAQ are viable only while you are allowed to modify the global operator new and operator delete. If you are not allowed to alter these global functions, the primary three quarters of this FAQ is still applicable.

Request for Solution File

Ask an Expert for Answer!!
C/C++ Programming: problem 1 plugging the memory leak when you
Reference No:- TGS0217515

Expected delivery within 24 Hours