Possible memory leak without a virtual destructor?
Asked Answered
E

4

11
#include <iostream>
using namespace std;
class base
{
   int a;
 public: 
   base() {a =0;}
 };
 class derv :public base
 {
   int b;
  public:
   derv() {b =1;}
 };
 int main()
 {
    base *pb = new derv();
    delete pb;
 }

I don't have a virtual destructor in derv class, does it delete only base part of derv object??

Extraterritorial answered 6/1, 2012 at 0:58 Comment(3)
It's the base class that needs a virtual destructor.Apathy
@Mysticial: James has this one.Albaugh
@James, You said even base class doesn't have any virtual function but it must have a virtual destructor if we want to inherit the base class??Extraterritorial
W
21

It might.

Because base does not have a virtual destructor, your code exhibits undefined behavior. Anything might happen. It might appear to work as you expect. It might leak memory. It might cause your program to crash. It might format your hard drive.

A citation was requested. C++11 §5.3.5/3 states that, for a scalar delete expression (i.e., not a delete[] expression):

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.

The static type (base) is different from the dynamic type (derv) and the static type does not have a virtual destructor, so the behavior is undefined.

Wildebeest answered 6/1, 2012 at 1:0 Comment(18)
Lack of virtual destructor in base means that any custom finalization specified in derived classes' destructor won't execute. The memory for object would still be deallocated correctly. (The destructor of base would also get called if it was defined by non-virtual).Fimbriation
@Xion: Really. The behavior is undefined. All bets are off. Nothing can be said with certainty about the behavior of the program.Wildebeest
@wilhelmtell: It is Standard. It's undefined behaviour.Albaugh
@wilhelmtell: If you insist. :-)Wildebeest
base::~base() will be called, derv::~derv() won't. Memory will be freed after that.Stanislaw
Intriguing. Yet another case where in practice UB gets swept under the rug by lenient compilators. Good to know!Fimbriation
@JamesMcNellis thanks, that clears all doubt and should reverse all the downvotes!Dossal
@James, You said even base class doesn't have any virtual function but it must have a virtual destructor if we want to inherit the base class??Extraterritorial
@Xion: Actually, it is not that much swept under the rug. Eg. if the base subobject isn't at the same address as the derived object, it will call deallocation function with the wrong address. Moreover, any virtual bases' destructors might be called wrongly (with wrong this), although I'm not that sure about this one.Buitenzorg
@Alok: If you delete a dynamically allocated derv object using a base* that points to it, then base must have a virtual destructor.Wildebeest
@Alok: No, but you must delete the classes' objects only through the pointers to most derived subobjects then.Buitenzorg
@Buitenzorg Is there a single compiler that actually places base members after derv members? The memory layout is, indeed, unspecified by the Standard, but in practice base is always placed first. We are talking about non-virtual inheritance here.Stanislaw
@AzzA: I cannot answer your question, as I am not familiar with every single compiler. (I'm sure I couldn't even name them all!)Wildebeest
@wilhelmtell: I agree. It is occasionally useful to provide citations. However, they can also make an answer confusing or overly technical and pedantic, so I tend to avoid doing so unless one is requested (which is fine) or the subject requires it, e.g. if some nuance needs to be pointed out or if there is disagreement as to what the correct behavior is.Wildebeest
@JamesMcNellis I agree with your answer in principle - the simple code posted will, most likely, never go wrong, but it's extremely unwise to rely on compiler implementations. Even if they seem to be same for all compilers. I was just simply curious, if someone actually knew of compiler that does "unusual" memory placement.Stanislaw
@Xion: What do you mean swept under the rug? Note that the standard explicitly states a compiler does not have to report when it compiles undefined behavior.Fragment
This issue comes up again and again on StackOverflow. Is there a way to fix this in C++2x? Should C++ split pointer types into two types? Instead of T*, there should be deletable_ptr<T> and notdeletable_ptr<T>. (I'm aware that the modern approach might use something like shared_ptr<T>, but I'm looking for a 'thin' approach that is closest to the underlying pointer, but which still ensures correctness). new shall return a deletable_ptr, and any attempt to convert to a base class shall convert to an undeletable_ptr, unless the base has a virtual constructor of course.Dario
@AaronMcDaid: There's nothing to stop you from making your own utilities, of course, but at some point either the language will change so much or your utility use will be so wide-spread that you're no longer programming in C++. What you're asking for is a "real" static and strong type-system, which is pretty much a lost cause in C++.Fragment
A
1

In your source code there is no memory leak, since you don't have any member variable that is created dynamically.

Consider the modified example below Case 1:

#include <iostream>
using namespace std;
class base
{
   int a;
 public: 
   base() {a =0;}
   ~base() 
     {
       cout<<"\nBase Destructor called";

     }
 };
 class derv :public base
 {
   int *b;

  public:
   derv() { b = new int;}
  ~derv()
  {
      cout<<"\nDerv Destructor called"; 
      delete b;
  }
 };
 int main()
 {
    base *pb = new derv();
    delete pb;
 }

In this case the output will be,

   Base Destructor called

In this case there is a memory leak, because 'b' is created dynamically using 'new' which should be deleted using 'delete' keyword. Since derv destructor is not being called it's not deleted so there is memory leak.

Consider the below case 2:

#include <iostream>
using namespace std;
class base
{
   int a;
 public: 
   base() {a =0;}
   virtual ~base() 
     {
       cout<<"\nBase Destructor called";

     }
 };
 class derv :public base
 {
   int *b;

  public:
   derv() { b = new int;}
  ~derv()
  {
      cout<<"\nDerv Destructor called"; 
      delete b;
  }
 };
 int main()
 {
    base *pb = new derv();
    delete pb;
 }

In the case 2 output will be,

Derv Destructor called 
Base Destructor called

In this case there is no memory leak.because derv destructor is called and b is getting deleted.

Destructor can be defined as Virtual in base class to make sure the derived class destructor to be called when we delete base class pointer which is pointing to derived class object.

We can say 'Destructor must be virtual when derived class has dynamically created members'.

Allembracing answered 6/1, 2012 at 0:58 Comment(2)
The behavior is undefined. All bets are off. Nothing can be said with certainty about the behavior of the program.Wildebeest
@JamesMcNellis: In the above code which has memory allocation in derived class should have virtual destructor. Otherwise there is a memory leak.Allembracing
A
0

To add to the discussion. GCC will throw an error if you ask it to do memory sanitizing on code where the destructor is not defined as virtual. You need to use

-fsanitize=address

as a compile option.

For example

#include <cstdio>

class Base
{
public:
 Base()
 {
    printf("ctor Base\n");
 }

  ~Base()
 {
    printf("dtor Base\n");
 }
};

class Derived : public Base {
private:
  double val;

public:
 Derived(const double& _val)
 : val(_val)
 {
    printf("ctor Derived\n");
 }

 ~Derived()
 {
    printf("dtor Derived\n");
 }
};

int main() {
    Derived* d = new Derived(5);
    Base* p = d;
    // Derived destructor not called!!
    delete p;
    //delete d;
}

when compiled will output the following error

ASM generation compiler returned: 0
Execution build compiler returned: 0
Program returned: 1
=================================================================
==1==ERROR: AddressSanitizer: new-delete-type-mismatch on 0x602000000010 in thread T0:
  object passed to delete has wrong type:
  size of the allocated type:   8 bytes;
  size of the deallocated type: 1 bytes.
    #0 0x7fc6ed4db428 in operator delete(void*, unsigned long) (/opt/compiler-explorer/gcc-13.1.0/lib64/libasan.so.8+0xdc428) (BuildId: c9b24be17e4cbd04bdb4891782c4425e47a9259a)
    #1 0x401177 in main /app/example.cpp:38
    #2 0x7fc6ece58082 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x24082) (BuildId: 1878e6b475720c7c51969e69ab2d276fae6d1dee)
    #3 0x4011fd in _start (/app/output.s+0x4011fd) (BuildId: 2751b7982ec6bd6f9c9092db77c9bfa142055e2d)

0x602000000010 is located 0 bytes inside of 8-byte region [0x602000000010,0x602000000018)
allocated by thread T0 here:
    #0 0x7fc6ed4da528 in operator new(unsigned long) (/opt/compiler-explorer/gcc-13.1.0/lib64/libasan.so.8+0xdb528) (BuildId: c9b24be17e4cbd04bdb4891782c4425e47a9259a)
    #1 0x40112f in main /app/example.cpp:35

SUMMARY: AddressSanitizer: new-delete-type-mismatch (/opt/compiler-explorer/gcc-13.1.0/lib64/libasan.so.8+0xdc428) (BuildId: c9b24be17e4cbd04bdb4891782c4425e47a9259a) in operator delete(void*, unsigned long)
==1==HINT: if you don't care about these errors you may set ASAN_OPTIONS=new_delete_type_mismatch=0
==1==ABO 

See https://godbolt.org/z/aaTrovaPE

If however you declare a virtual destructor on base then the error goes away.

Adversative answered 26/6, 2023 at 13:36 Comment(0)
B
-1

There is no memory leak in your code. There would have been a memory leak if you needed to free some memory in the derived class destructor.

Buccal answered 6/1, 2012 at 1:6 Comment(1)
The behavior is undefined. All bets are off. Nothing can be said with certainty about the behavior of the program.Wildebeest

© 2022 - 2024 — McMap. All rights reserved.