4

I am developing a tool like memory leak detector. I can track the placement new but how can i track the placement delete. I did a lot of R & D and i found that placement delete cant be called directly, it is called by constructor at the time of exception. So how can i keep the track of placement delete against placement new?

Any help would be appreciated......

max_dev
  • 161
  • 11
  • It's not completely clear what you are trying to do. What do you mean by "keep the track of"? – CB Bailey Oct 27 '10 at 06:39
  • So what you're saying is that when an object is constructed with placement `new`, you want to keep track of this fact and ensure that its destructor is explicitly invoked before the memory is reclaimed? – Jon Purdy Oct 27 '10 at 07:02
  • +1 for person placing the question being more knowledgeable about the subject than those who have answered so far. He he. – Cheers and hth. - Alf Oct 27 '10 at 07:42
  • @Roger: "placement new" is a term defined by the Holy Standard essentially meaning an `operator new` taking additional arguments. Among other things it's used for constructing an object in preallocated storage (your answer), and for debugging (OP's question). E.g. MFC uses it for debugging in debug builds; that they forgot to define the corresponding placement delete is one of the most infamous MFC bugs, causing memory leaks only in debug builds... Heh. Cheers, & hth., – Cheers and hth. - Alf Oct 27 '10 at 07:56
  • @Alf: The standard is vague and uses the term inconsistently. See my comment on GMan's answer which explains this. I only saw your first comment because I was checking to see if the OP had clarified how *he's* using it or not. –  Oct 27 '10 at 08:00
  • @Roger: Nope, you're interpreting the text (incorrectly). Look for the term set in *italics*. That's by convention the definition. Cheers & hth., – Cheers and hth. - Alf Oct 27 '10 at 08:02
  • @Alf: "Placement new" occurs twice in normative text in C++03 that I see, neither time is in italics, and both times are explicitly talking about the (size_t, void*) overloads only. –  Oct 27 '10 at 08:11
  • @Roger: it's C++98 §5.3.4/11, and there it's spelled *new-placement*. No doubt to confuse readers. :-) I suggest you look there. §3.2/2 uses the informal term "placement new" but provides reference to §5.3.4 to clarify. §18.4.1.3/8 talks about "the library's" specific placement new operator. That is indeed a placement new operator used to construct an object in preallocated storage. The general term "placement new" (again, see §5.3.4) derives from this basic usage. Cheers, – Cheers and hth. - Alf Oct 27 '10 at 08:27
  • @Alf: So, in other words, you tell me to *read the* fine *standard and look for the definition in italics,* and then you say there's instead a convention of deriving a general term from non-specific usage, and that it never actually appears in italics at all? (Just so I'm clear on this.) –  Oct 27 '10 at 08:33
  • @Roger: yes. Mea culpa, sorry. The advice about italics holds in general. But when you didn't find it that way I looked it up for you. Hope that counts as at least partial compensation for not doing that in the first place? Otoh., advice about italics generally good, while direct pointer to para only good for this particular question (all that stuff about teaching to fish instead of giving a man a fish etc., "Teach a man to set a fire and it'll keep him warm for a day. Set him on fire on it'll keep him warm for the rest of his life."). Cheers, – Cheers and hth. - Alf Oct 27 '10 at 08:37
  • @Alf: Teaching someone to fish is one thing; telling them they should look for the fish a mile from water is another. What I've said from the beginning still stands: the standard uses "placement new" inconsistently. Without clarification from the OP, we can do no more than provide a best guess. Doing that and getting indirectly told you have no idea what you're talking about (as you did) does share something with a fire, yes. –  Oct 27 '10 at 08:43
  • @Roger: no, no, and no, sorry. First, I didn't tell you at first to go a specific place. I did that when you failed to find the relevant paragraph. For that, I could conceivably still be waiting for a "thanks". Secondly, the standard does not use "placement new" inconsistently. Third, asserting that the standard is wrong, by way of a fallacious argument that I don't know what I'm talking about (or ditto for someone else), is a position and way of argumentation that I think you would find it useful to reconsider. Cheers & hth. – Cheers and hth. - Alf Oct 27 '10 at 08:50
  • 1
    @jon: Yes, this is what i want. – max_dev Oct 27 '10 at 09:31
  • 1
    @max_dev: You must modify the type involved to support that type of tracking. –  Oct 27 '10 at 09:49

6 Answers6

10

You want to pair allocation and deallocation:

  • malloc / free
  • new / delete (the "regular" forms)
  • new[] / delete[]

But what do you pair with placement new? (Explicitly: the one that takes a void* and commonly called simply "placement new", instead of other placement forms of new.) It's not delete, but an explicit destructor call.

  • T *p = new(mem) T(); / p->~T()

Placement new doesn't actually allocate anything, it's just syntactic sugar for calling a constructor. You don't need to, and shouldn't, track it. It's even a bit weirder than other forms, as it's not unusual to call the "destroy" bit first, then replace the destroyed object with another (the opposite of the sequence for others):

{
  T some_object;

  some_object->~T(); // die! die! die!
  new(&some_object) T();  // didn't save the return value? memory leak..? nope.
} // some_object leaves scope and is destructed (again)
  • @Roger: Nicely explained and completely agree. This reminds me when using placement new is about the only scenario we would call an destructor explicitly. – Alok Save Oct 27 '10 at 07:31
  • +1 totally agree, placement new doesn't actually allocate any memory so there is nothing to delete (or keep track of). I believe OPs problem lies in incorrect memory management within constructors. – Chris Bednarski Oct 27 '10 at 10:12
  • This is a great comment, but I'm pretty sure the OP is asking about new-placement semantics for the sake of debugging (`void * operator new ( std::size_t size, char const * const filename )`, etc.). See my answer below. – lmat - Reinstate Monica Feb 12 '14 at 20:27
  • 1
    -1 "Placement new doesn't actually allocate anything, it's just syntactic sugar for calling a constructor" is incorrect. while it does hold for the special case of a placement new provided by the standard library, it does not hold for placement new in gneeral. – Cheers and hth. - Alf Feb 12 '14 at 22:48
2

First, not to the OP but to other readers: as I understand it the OP is not talking about constructing an object in preallocated storage.

And I'm not talking about that.

To OP: you don't, just let your placement form of operator delete forward to the ordinary operator delete. After all that's the deallocation function that will be called for any successfully constructed dynamically allocated object. So you need to support it no matter what.

If you want to associate debug information with the allocated memory for use in the deallocation function then one practical option is to allocate a bit more than requested and place the info at the start or end of that block, return pointer to the unused portion. Then operator delete needs to do the opposite. Caution: alignment.

I guess an impractical option is to use a static std::map (or other associative array, like a hash table). It runs in thread safety issues and such, but it avoids alignment issue.


Addendum, a complete example:

// Note: while this example works nicely, it doesn't support threading.
#include <iostream>
#include <new>          // std::bad_alloc
#include <stddef.h>     // ptrdiff_t, size_t, max_align_t
#include <stdlib.h>     // malloc, EXIT_*,

typedef unsigned char Byte;

struct Source_reference
{
    char const* filename;
    int         line_number;
};

#define SOURCE_REF  Source_reference{ __FILE__, __LINE__ }

auto operator<<( std::ostream& stream, Source_reference const& ref )
    -> std::ostream&
{
    if( ref.filename == nullptr )
    {
        return stream << "[unknown source location]";
    }
    return stream << "\"" << ref.filename << "\"@" << ref.line_number;
}

struct Block_info
{
    Source_reference    source_ref;
    Block_info*         p_prev;
    Block_info*         p_next;

    void link_in_after( Block_info& predecessor )
    {
        p_prev = &predecessor;
        p_next = predecessor.p_next;
        predecessor.p_next = this;
        p_next->p_prev = this;
    }

    void unlink()
    {
        p_next->p_prev = p_prev;
        p_prev->p_next = p_next;
    }
};

namespace g {
    size_t const    max_align   = sizeof( max_align_t );
    size_t const    prefix_size =
        ((sizeof( Block_info ) + max_align - 1)/max_align)*max_align;
    Block_info      block_list_header   =
        {{nullptr,0}, &block_list_header, &block_list_header};
}  // namespace g

auto tracking_alloc( size_t const n_bytes_requested )
    -> void*
{
    size_t const n_bytes = n_bytes_requested + g::prefix_size;
    Byte* const result = static_cast<Byte*>( malloc( n_bytes ) );
    if( !result ) { throw std::bad_alloc(); }

    Block_info* const p_info = ::new( result ) Block_info();
    p_info->link_in_after( g::block_list_header );

    return result + g::prefix_size;
}

void tracking_dealloc( void* p )
{
    Block_info* p_info  = reinterpret_cast<Block_info*>(
        static_cast<Byte*>( p ) - g::prefix_size
        );
    p_info->unlink();
    free( p_info );
}

auto operator new( size_t const n_bytes )
    -> void*
{ return tracking_alloc( n_bytes ); }

auto operator new[]( size_t const n_bytes )
    -> void*
{ return operator new( n_bytes ); }

void operator delete( void* p )
{ tracking_dealloc( p ); }

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

auto operator new( size_t const n_bytes, Source_reference const& ref )
    -> void*
{
    Byte* const p               = static_cast<Byte*>( operator new( n_bytes ) );

    Block_info* const p_info    = reinterpret_cast<Block_info*>( p - g::prefix_size );
    p_info->source_ref = ref;

    return p;
}

void operator delete( void* p, Source_reference const& )
{
    using namespace std;
    cout << "!placement delete called." << endl;
    operator delete( p );
}

void list_blocks()
{
    using namespace std;
    cout << "Known allocated blocks:" << endl;
    for(
        Block_info* p_info = g::block_list_header.p_next;
        p_info != &g::block_list_header;
        p_info = p_info->p_next
        )
    {
        void* const p_data = reinterpret_cast<Byte*>( p_info ) + g::prefix_size;
        cout
            << "- Basic allocation " << p_data
            << " from " << p_info->source_ref << "."
            << endl;
    }
    cout << "- End list." << endl;
}

#include <vector>
auto main()
    -> int
{
    using namespace std;

    int* p = new( SOURCE_REF ) int( 42 );
    cout << "An int allocated with ref at " << p << "." << endl;
    list_blocks();

    int* p2 = new int( 43 );
    cout << "\nAn int allocated sans ref at " << p << "." << endl;
    list_blocks();

    {
        vector<double> v( 3 );
        cout << "\nA vector constructed" << endl;
        list_blocks();

        try
        {
            struct Ungood{ Ungood() { throw 666; } };
            cout << "\nAllocating with ref an object that fails construction." << endl;
            new( SOURCE_REF ) Ungood;
        }
        catch( ... )
        {}
        list_blocks();

        delete p;
        cout << "\nThe int-with-ref deleted." << endl;
        list_blocks();
    }
    cout << "\nThe vector destroyed" << endl;
    list_blocks();

    delete p2;
    cout << "\nThe int-sans-ref deleted." << endl;
    list_blocks();
}

Output with MinGW g++ 4.8.2:

An int allocated with ref at 0x213c0.
Known allocated blocks:
- Basic allocation 0x213c0 from "foo.cpp"@134.
- End list.

An int allocated sans ref at 0x213c0.
Known allocated blocks:
- Basic allocation 0x21410 from [unknown source location].
- Basic allocation 0x213c0 from "foo.cpp"@134.
- End list.

A vector constructed
Known allocated blocks:
- Basic allocation 0x21460 from [unknown source location].
- Basic allocation 0x21410 from [unknown source location].
- Basic allocation 0x213c0 from "foo.cpp"@134.
- End list.

Allocating with ref an object that fails construction.
!placement delete called.
Known allocated blocks:
- Basic allocation 0x21460 from [unknown source location].
- Basic allocation 0x21410 from [unknown source location].
- Basic allocation 0x213c0 from "foo.cpp"@134.
- End list.

The int-with-ref deleted.
Known allocated blocks:
- Basic allocation 0x21460 from [unknown source location].
- Basic allocation 0x21410 from [unknown source location].
- End list.

The vector destroyed
Known allocated blocks:
- Basic allocation 0x21410 from [unknown source location].
- End list.

The int-sans-ref deleted.
Known allocated blocks:
- End list.
Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
  • Someone who preferred to be anonymous voted this (correct) answer down. Please always use a comment to explain why you vote something down. It will help others. – Cheers and hth. - Alf Oct 28 '10 at 03:40
  • I didn't vote it down, but I don't think you answered the question. Say I keep a `vector` (or `map` as you suggest) tracking each time an object is allocated along with the file and line. How do I maintain this list at delete time ? I would like to have `delete(void *, char const *)` so that I can say where it was deleted, then just call the standard deallocator. It looks like the OP doesn't want to replace `operator delete` (nor would I). – lmat - Reinstate Monica Feb 12 '14 at 20:32
  • @LimitedAtonement: The extra requirement of not replacing `operator delete`, i.e. of not tracking deallocations, contradicts the requirement of tracking them, for the general case. IOW, and considering that only the general case is of interest here (for ideas like manually adding tracking calls throughout the code are not practical), your interpretation of the question, the "Looks like", is most probably incorrect, for it's doubtful that the OP intended to be self-contradictory. But if you're right then there is no technical solution: only a requirements adjustment can fix the contradiction. – Cheers and hth. - Alf Feb 12 '14 at 22:43
  • "manually adding tracking calls", those tracking calls should track uses of `new` and `delete`, right ? If so, `new` and `delete` must be redefined or replaced. `new` is easy: just create a `void * operator new (size_t s, char const *filename)`, then `#define new new(__FILE__)` which will make calls to `new` use the overloaded version. Now, as for delete... the only recourse is to replace the default, but with what!? `void operator delete (void * p) { /*tracking code*/ ::operator delete(p);}` would be nice, but is recursive. Does this make sense? – lmat - Reinstate Monica Feb 13 '14 at 15:43
  • I'll add that it's not safe to `void operator delete (void * p) { /*tracking*/ free (p);}` since the definition of our overloaded `operator new` surely calls `::operator new(s);`. If we don't call `::operator new` within the overload, but instead come up with our own, then naturally, we're free to come up with our own delete, but I would rather not take on the task of defining `operator new` myself if possible. Personally, I've taken the approach of having a widely-used base class that tracks its own allocations and deletions. – lmat - Reinstate Monica Feb 13 '14 at 15:45
  • @LimitedAtonement: The SO comment utility is (unfortunately by design) not well suited for a long discussion or for an introduction to the subject, whichever it is, so I just added an example. It is just one of a zillion ways to go about it. But hopefully clears up what you're wondering about. – Cheers and hth. - Alf Feb 13 '14 at 20:53
1

You don't, there's nothing to keep track of.

Placement new means putting an object at a memory location. The storage for it has already been allocated. You don't call any form of delete on the object itself because that's the job of where you got the storage from.

That is, in this:

void* memory = operator new(sizeof(int)); // A
new (memory) int; // B
operator delete(memory); // C

A and C are what you should be tracking, not B.

GManNickG
  • 494,350
  • 52
  • 494
  • 543
  • Its fine when we use placement new for int, float, string but when i use placement new for my class and in the constructor of that class, there is one more new statement occur like: class MyClass { private: char * a; public: MyClass(char * c) { a =new char[10]; strcpy(a, c); } ~MyClass() { delete[] a; } }; then if we direct delete the memory(in your example) then there will be a memory leak of 10 bytes. To prevent it we have to call the destructor. – max_dev Oct 27 '10 at 06:56
  • 1
    That's only what a placement new using the standard library reserved placement form of operator new does. In general, a placement new expression is just a mechanism to pass more parameters to an allocation function. Most placement new expressions will cause memory to be allocated. – CB Bailey Oct 27 '10 at 07:08
  • @max: Right, and that should be wrapped up. If you're placement newing, it's your job to make sure things are done right. In good code, that'll be wrapped into a resource so cleanup happens automatically. – GManNickG Oct 27 '10 at 07:14
  • @Charles: The standard's a bit vague, as the std::nothrow versions, for example, are called "placement version of a new expression" but *aren't* in 18.4.1.3 Placement forms, which does say "placement new" as if it is a single entity. –  Oct 27 '10 at 07:15
  • @Roger Pate: I'm not sure I really agree that there's much vagueness on this point. There are frequent references to _a_ placement allocation function and _a_ placement deallocation function other than the library ones taking `void*`. The optional bit in the _new-expression_ grammar is called _new-placement_ and there are further references to "placement version of a _new-expression_" other than in the context of using the standard placement forms from 18.4.1.3. I don't think that referring to a "placement version of a _new-expression_" as a "placement new expression" is too much of a leap. – CB Bailey Oct 27 '10 at 08:30
  • @Charles: It's not too much of a leap, but this specific new overload is the only form the standard puts in the "placement forms" section and the one most talked about: it's not unreasonable to believe someone means this form when they say "placement new", unless they or their code clarifies. Inconsistent might get my point across better than vague. –  Oct 27 '10 at 08:46
  • @max_dev: placement new does not allocate memory, ever. Calling char* memory = new char[100]; X* x = new (memory) X(); Y* y = new (memory) Y(); delete [] memory; will not, in itself, leak memory (and of course using x after constructing y would be bad since its memory has been trampled over). If, however, like you said X::X() or Y::Y() dynamically allocated memory then they would leak, because the dtor is not called - but you already track this in your leak detector because you overloaded operator new and delete. It is up to the code using placement new to manually call x->~X(); and y->::~Y(); –  Jan 20 '11 at 02:47
0

1) use placement new for new allocs

2) use a wrapper function (such as) delete_object which calls through your allocator, and one for delete_array. note that the pointer's alignment may be offset from the actual allocation you returned (for arrays).

justin
  • 104,054
  • 14
  • 179
  • 226
0

declare operator new/new[]/delete/delete[], (as well as the builtin variants, or hide them) in the interface of a base class. then implement the ones you'll need.

this should be usable for some purposes, if all you want to do is track leaks of objects within a specific class hierarchy (or set of).

illustration (beware, pseudo code follows):

/* option 1) use a specified allocator for all class instances */
namespace { const char* const TypeID("MON::t_object"); }

void* MON::t_object::operator new(size_t size) {
    void* const allocation(AllocatorForType(TypeID).operator_new(size));
    std::cout << "MON::t_object::operator new - size: " << size << " TypeID: " << TypeID << " address: " << allocation << "\n";
    return allocation;
}

/* option 2) use a specific allocator via placement, or type overload */
void* MON::t_object::operator new(size_t size, t_allocator& allocator) {
    void* const allocation(allocator.operator_new(size));
    std::cout << "MON::t_object::operator new - size: " << size << " allocator: " << & allocator << " address: " << allocation << "\n";
    return allocation;
}

void MON::t_object::operator delete(void* allocation) {

    std::cout << "MON::t_object::operator delete: " << allocation << "\n";

    /* now call through your allocator interface */
    if ( /* using option 1 */ ) {
        AllocatorForType(TypeID).operator_delete(allocation);
    }
    else /* using option 2 */ {
        AllocatorForAllocationWithAddress(allocation).operator_delete(allocation);
    }
}
justin
  • 104,054
  • 14
  • 179
  • 226
  • i have already done for new/new[]/delete/delete[] and placement new and i am able to keep track of those, but noy able to keep track of placement delete – max_dev Oct 27 '10 at 07:29
  • 1
    yes, as you mentioned in your OP, placement delete will be called only when there is an exception in `new` - not when `delete` is called. response updated with example. – justin Oct 27 '10 at 08:45
  • note that the example only covers singular allocations - you'll also need implementations for operators `new[]` and `delete[]` – justin Oct 27 '10 at 08:48
0

I have a comment regarding one of your examples:

MyClass(char * c)
{
  a = new char[10];
  strcpy(a, c);
  throw here ...
}
~MyClass()
{
  delete[] a;
}

I think your problem lies in using new within constructors without wrapping it in some sort of a resource manager. If the constructor throws, no matter how an object has been newed within it (new or placement new), the memory allocated to it will leak unless it is managed by another object.

struct Janitor {
    Janitor(char* item) : mgr(item) {}
    ~Janitor() { if uncaught_exception() delete [] mgr; }
    char *mgr;
};

MyClass(char * c)
{
   Janitor j(new char[10]);
        // j is destroyed both if the rest of the contstructor succeeds
        // and if it throws

   //unsafe code
   strcpy(j.mgr, c);
   ...

   //at the end
   //pass the resource management to MyClass
   a = j.mgr;
};

Some of these may be helpful as well.
http://www.gotw.ca/gotw/008.htm
http://www.gotw.ca/gotw/056.htm
http://www.gotw.ca/gotw/010.htm
http://www.gotw.ca/gotw/022.htm
http://www.gotw.ca/gotw/042.htm

Chris Bednarski
  • 3,364
  • 25
  • 33