6

In the following code, when ptr is deleted, the destructor for Base is called, but not the destructor for Derived (due to destructor of Base not being virtual).

class Base { int b; }; class Derived : public Base { int d; }; int main(void) { Base * ptr = new Derived(); delete ptr; return 0; } 

Valgrind reports that the program contains no memory leaks, which i guess is true in the sense that all newed data is deleted in this particular case. My question is - given that the (default) destructor of Derived is not called, when and how is the memory for d deallocated or reclaimed?

4
  • 3
    I believe, the memory is reclaimed due to delete ptr, it's just that destructor is not invoked, which could be a potential undefined behavior. Commented Feb 22, 2013 at 11:24
  • 5
    It's not potential. It is undefined behaviour. (§5.3.5/3) Commented Feb 22, 2013 at 11:33
  • 2
    Your valgrind call says "no memory leaks", my MemoryLogger says allocated 8 bytes at 0x3b2380; deallocated 4 bytes at 0x3b2380; Error: 4 bytes still allocated!. UB. Potentially this could also say Have a nice day sir. Commented Feb 22, 2013 at 11:34
  • @iammilind: It's potential leak as Derived might have been responsible for some further resources that would thus not be released. Commented Feb 22, 2013 at 12:17

3 Answers 3

7

It is Undefined behavior to call delete on a base class pointer pointing to a derived class object if the destructor in base class is not virtual.

Undefined behavior means that anything can happen. Whether memory leaks or not is irrelevant the fact is that your program can show any behavior and you cannot rely on any behavior that is shown.

For your code to be valid the destructor in Base class must be virtual.


C++11 Standard 5.3.5 Delete:
Para 3:

In the first alternative (delete object), if the static type of the object to be deleted is different from its dynamic type, the static type shall be a base class of the dynamic type of the object to be deleted and the static type shall have a virtual destructor or the behavior is undefined. In the second alternative (delete array) if the dynamic type of the object to be deleted differs from its static type, the behavior is undefined.

Sign up to request clarification or add additional context in comments.

Comments

1

Calling the incorrect destructor, I think, is undefined behaviour. You should have a virtual destructor.

However, since the allocation is done in one block for the Derived class, it "works" because the internals of delete simply keeps track of the size of the allocation, and thus frees the 8 bytes that the object takes up, instead of the four that Base would use.

Note that the virtual destructor is not responsible in itself of FREEING the memory that this points to. That happens in delete after the destructor has been called.

2 Comments

It may well be that this particular implementation of delete keeps track of the size of the allocation, but that is not required and the fact that it seems to work may be just coincidence. In particular, memory managers often have a minimum block size that they will allocate, and it could be that the block allocated for a Derived object is the same size as the block allocated for a Base object. In any event, undefined behavior is simply undefined; speculating about why it might happen to not show symptoms in a particular case is usually just guessing.
Oh, and while I'm on the subject, freeing the memory is often done in the destructor; it gets a secret flag that tells it whether to also delete the memory. That makes it easier to deal with class-specific operator delete and different sizes of derived types. Sorry about the lecture...
0

As other great guys mentioned, the standard says it is undefined behavior, but usually amongst compilers that care about competition and users this is up to system allocator implementation, which can be different from platform to platform. The destructor itself does not free the memory of Derived object, which contains fields.

When you call new Derived, global implementation of operator new is called and it receives number of bytes to allocate. It will be sizeof(Derived) in your case.

Upon actual memory freeing (global operator delete), allocator will know how many bytes to free from the address of the object being freed. So, the memory used by both b and d will be reclaimed

6 Comments

No, you can't count on the memory being freed. The comments are correct, this is Undefined behavior. You can't predict what will happen then.
@MSalters Yes, you are right. I described how it used to work for me - did not know it is just a convenience..
@MSalters: Roman just tried to explain what most probably happened in this particular case with this particular compiler and why valgrind didn't detect any memory leak. He didn't say it's not UB.
@AndyT: He stated that "the memory used ... will be reclaimed". Describing the definitive behavior in a given case necessarily means the absence of UB. (And describing a possible behavior in the case of UB is senseless since anything is then possible)
Right. The answer would have been good if it haven't been missing the important word usually. Usually it's exactly as described, but various things may interfere. Class-specific allocators certainly would.
|