没有虚析构函数可能会导致内存泄漏吗?

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;
 }

我在派生类中没有虚析构函数,它是否只删除了派生对象的基类部分?


1
需要一个虚析构函数的是基类。 - Yuushi
@Mysticial:詹姆斯已经处理了这个。 - Puppy
@James,你说即使基类没有任何虚函数,但如果我们想要继承基类,它必须有一个虚析构函数? - Alok
4个回答

21

可能会发生这种情况。

由于base没有虚析构函数,您的代码表现出未定义行为。任何事情都可能发生,它可能看起来像您期望的那样工作,也可能泄漏内存,导致程序崩溃,甚至格式化硬盘。

有人要求引用出处。C++11 §5.3.5/3规定,对于一个标量delete表达式(即不是delete[]表达式):

如果要删除的对象的静态类型与其动态类型不同,则静态类型应是要删除的对象的动态类型的基类,并且静态类型应具有虚析构函数,否则其行为是未定义的。

静态类型(base)不同于动态类型(derv),而静态类型没有虚析构函数,因此行为未定义。


2
基类缺少虚析构函数意味着任何在派生类的析构函数中指定的自定义终止操作都不会被执行。对象的内存仍然会被正确地释放。(如果非虚函数析构函数被定义,也会调用base类的析构函数)。 - Xion
6
@Xion: "确实。这种行为是未定义的,一切皆不确定。关于程序的行为不能确定地说出任何事情。" - James McNellis
2
@wilhelmtell:这是标准。这是未定义的行为。 - Puppy
将调用base::~base(),不会调用derv::~derv()。此后内存将被释放。 - lapk
1
有趣。又是一个实践中编译器宽容地掩盖了UB的案例。好知道! - Xion
显示剩余13条评论

1
在您的源代码中没有内存泄漏,因为您没有创建任何动态成员变量。
考虑下面修改后的示例 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;
 }

在这种情况下,输出将是:
   Base Destructor called

在这种情况下,存在内存泄漏,因为“b”是使用“new”动态创建的,应使用“delete”关键字进行删除。由于未调用derv析构函数,因此它未被删除,因此存在内存泄漏。
考虑下面的情况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;
 }

在第二种情况下,输出将是:
Derv Destructor called 
Base Destructor called

在这种情况下,没有内存泄漏。因为派生类析构函数被调用并且b被删除。
析构函数可以在基类中定义为虚函数,以确保当我们删除指向派生类对象的基类指针时,派生类析构函数被调用。
我们可以说“当派生类具有动态创建的成员时,析构函数必须是虚拟的”。

1
行为未定义。一切皆不确定。无法确定程序的行为。 - James McNellis
1
@JamesMcNellis:在上面的代码中,派生类中有内存分配的情况下应该有虚析构函数。否则会导致内存泄漏。 - srajeshnkl

0

在讨论中补充一点。如果您要求GCC对没有定义为虚拟的析构函数的代码进行内存清理,它将抛出错误。您需要使用

-fsanitize=address

作为编译选项

例如

#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;
}

编译时将输出以下错误

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 

查看https://godbolt.org/z/aaTrovaPE

然而,如果你在基类上声明一个虚析构函数,错误将消失。


-1

你的代码中没有内存泄漏。如果你需要在派生类析构函数中释放一些内存,那么就会出现内存泄漏。


7
行为未定义。一切皆不确定。无法确定程序的行为,没有任何可以肯定的说法。 - James McNellis

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接