tags:

views:

553

answers:

7

Is it possible to zero out the memory of deleted objects in C++? I want to do this to reproduce a coredump in unit test:

//Some member variable of object-b is passed-by-pointer to object-a
//When object-b is deleted, that member variable is also deleted
//In my unit test code, I want to reproduce this
//even if I explicitly call delete on object-b
//accessBMemberVariable should coredump, but it doesn't
//I'm assuming even though object-b is deleted, it's still intact in memory
A *a = new A();
{
  B *b = new B(a);
  delete b;
}
a->accessBMemberVariable();
A: 

Once you've deleted b you don't really have permission to write over where it was. But you usually can get away with doing just that; and you'll see code where programmers use memset for this.


The approved way to do this, though, would be to call the destructor directly, then write over the memory (with, say, memset) and then call delete on the object. This does require your destructor to be pretty smart because delete is going to call the destructor. So the destructor must realize that the whole object is nothing but 0's and not do anything:

b->~B();
memset(b, 0, sizeof(b));
delete b;
Max Lybbert
This will crash if your malloc decides to shrink the heap, however...
bdonlan
Which is why I said "usually get away with this." The original post makes this sound like a one-time, today-only debugging exercise, not something that will ship.
Max Lybbert
What does that have to do with this, though? Especially when debugging, one needs reliable tools. What does it buy him when he's just adding more bugs on top of those he tries to fix?
Johannes Schaub - litb
I honestly can't figure out the value in zeroing out the memory in debug mode/unit test mode. But I do know the question was "I want to zero out the memory to see if a bug reappears when I do." And I know that in general this will work, even if it's illegal. And since it's not production code, I would be willing to commit this indiscretion myself.
Max Lybbert
+19  A: 

You probably should override the delete operator.

Example for the given class B:

class B
{
public:

  // your code
  ...

  // override delete
  void operator delete(void * p, size_t s)
  {
    ::memset(p, 0, s);
    ::operator delete(p, s);
  }
};

EDIT: Thanks litb for pointing this out.

beef2k
No good. Inside operator delete() the destructor has already been called. This will call it again.
jmucchiello
jmucchiello is right. the delete operator will call the destructor, and then it will call your operator delete. Inside it, you should just zero the memory and then delegate to the global operator delete (using ::operator delete(p);). @beef2k, you should have a first parameter of void*. If you want to overload the delete operator for B*, you have to write it as a member. You can't just make the first parameter a B* :) The best is, just write a "void operator delete(void*p, size_t s)" as a member. The compiler will call it with the size to deallocate. You can clear that many bytes out.
Johannes Schaub - litb
The size is not always the same as sizeof(B). In case you derive a class from B, then the compiler will call that member operator delete, in which case the size of the memory is sizeof(Derived) but not sizeof(B). You can make it a template: template<typename T> struct ZeroAllocated { void operator delete(void *p, size_t s) { ... } }; (also, consider overloading op new too, for consistency). Then struct B : ZeroAllocated<B> { .... }; i hope this makes sense :)
Johannes Schaub - litb
+2  A: 

Another poster suggested:

 delete b;
 memset(b,0,sizeof(B));

Please don't do this!!! Writes to address space that is returned to the memory manager are UNDEFINED!!!!

Even if your compiler and library let you get away with it now, it is bad bad bad. A change in library or platform, or even an update in the compiler will bite you in the ass.

Think of a race condition where you delete b, then some other thread makes an allocation, the memory at b is given out, and then you call memset! Bang, you're dead.

If you must clear the memory (which who cares) zero it out before calling delete.

 memset(b,0,sizeof(B));
 delete b;
James Caccese
Of course, now B's destructor is probably dead now, but that's a relatively minor issue. ;)
Arafangion
In fact, you have to zero out the memory *before* freeing the memory, but *after* calling the destructor. See my post with the overridden `delete` operator.
beef2k
Yup, if your dtor is virtual this will core dump alright, not in the way you wanted (it will happen on delete b, not a->accessBMemberVariable()), but GPF/core dump/bombing will follow...
Andreas Magnusson
Yeah, having the memset/delete in either order won't work very well.
Nathaniel Flath
+2  A: 

Use placement "new" if you can (http://www.parashift.com/c++-faq-lite/dtors.html#faq-11.10) and zero out the chunk you gave after calling the object destructor manually.

artificialidiot
+3  A: 

accessBMemberVariable should coredump, but it doesn't

Nah, why should it? It's quite possible that the memory that b used to occupy is now owned by the CRT, the CRT that your application owns. The CRT may opt to not release memory back to the OS. Core dumps will only happen if you access memory not owned by your application.

Zeroing out the memory occupied by b may not do you any good depending on the type of variable that A has the address of.

My advice would be to allocate B on the stack, that should bring out the fireworks... but then again, not quite in the way you'd expect...

So if you really want a core dump you should use the OS functions to allocate memory and free it:

char *buf = OS_Alloc(sizeof(B));
B *b = new(buf) B();
a->someBMember = &b->myMember;
b->~B();
OS_Free(buf);
a->accessBMemberVariable();
Andreas Magnusson
A: 

In your example you write

'A->accessBMemberVariable' is this a typo? shouldn't it be 'a->accessBMemberVariable' ?

Assuming it is a typo (otherwise the whole design seems a bit weird).

If you want to verify that 'a' is deleted properly it probably be better to instead change the way you handle the allocation and use auto_ptr's instead. That way you will be sure things are deleted properly:

auto_ptr<A> a( new A );
{
  auto_ptr<B> b( new B(a) ); // B takes ownership of 'a', delete at scope exit
}
a->accessBMemberVariable(); // shouldn't do to well.

and a constructor for B in the form of

B( auto_ptr<A>& a ) : m_a(a) {;}

where

auto_ptr<A> m_a
Anders K.
+1  A: 

Use the debugging malloc/new features in your environment.

On MSVC, link with the debug runtime libraries. On FreeBSD, set MALLOC_OPTIONS to have the 'Z' or 'J' flags, as appropriate. On other platforms, read the documentation or substitute in an appropriate allocator with debugging support.

Calling memset() after deletion is just bad on so many levels.

janm