析构函数无法删除已分配的连续内存块

时间:2016-12-22 15:20:07

标签: c++ memory destructor

以下代码编译但在运行时发送错误:

# include <iostream>
# include <string.h>

class A {
public:
  A() {}
  A ( int id, char * t_name ) {
    _id = id ;
    name = new char [ strlen (t_name) + 1 ] ;
    strcpy ( name, t_name ) ;
  }

  char *name ;
  int _id ;
  ~A() { delete []  name ;}
} ;

int main () {
  A a ( 1, "123" ) ;
  A b ;
  b = a ;

  std::cout << static_cast < const void * > ( a.name ) << std::endl ;
  std::cout << static_cast < const void * > ( b.name ) << std::endl ;

  b.name = "abc" ; // b.name is directed to a different memory block 
  std::cout << static_cast < const void * > ( a.name ) << std::endl ;
  std::cout << static_cast < const void * > ( b.name ) << std::endl ;
  std::cout << a.name << std::endl ;
  std::cout << b.name << std::endl ;

  return 0 ;
}

输出如下内容:

0x7ff87bc03200
0x7ff87bc03200
0x7ff87bc03200
0x10f9bcf64
123
abc
a.out(883,0x7fff7ee3d000) malloc: *** error for object 0x10f9bcf64:
pointer being freed was not allocated
*** set a breakpoint in malloc_error_break to debug
Abort trap: 6

我不明白为什么会说:

  

0x10f9bcf64:未分配的指针

因为b.name显然是针对0x10f9bcf64,并且不再与a重叠!

我也想知道如何解决这个问题?谢谢!

4 个答案:

答案 0 :(得分:3)

您应首先阅读Rule of 3/5/0。你的陈述:

b = a;

违反了规则3(如果您使用的是现代C ++,则为5,即C ++ 11或更高版本),因为您的类A有一个指针作为成员。

接下来,如果你考虑这个陈述:

b.name = "abc";

你在这里影响了一个你没有用new分配的静态字符数组。所以当你的析构函数试图删除它时:

~A() { delete []  name ;}

delete[]的调用会产生错误。

一个简单的解决方案是将name声明为std::string

class A {
public:
  A () {}
  A ( int id, const std::string& t_name ) {
    _id = id ;
    name = t_name;
  }

  std::string name ;
  int _id ;
} ;

int main () {
  A a ( 1, "123" ) ;
  A b ;
  b = a ;

  std::cout << static_cast < const void * > ( &a.name ) << std::endl ;
  std::cout << static_cast < const void * > ( &b.name ) << std::endl ;

  b.name = "abc" ; // b.name is directed to a different memory block 
  std::cout << static_cast < const void * > ( &a.name ) << std::endl ;
  std::cout << static_cast < const void * > ( &b.name ) << std::endl ;
  std::cout << a.name << std::endl ;
  std::cout << b.name << std::endl ;

  return 0 ;
}

由于std::string为你管理记忆,你会回到0的统治世界。

答案 1 :(得分:1)

对于初学者,构造函数声明应该看起来像

A ( int id, const char * t_name )
            ^^^^^^

因为您使用字符串文字来初始化类的对象,而字符串文字具有常量数组的类型。

默认的复制赋值运算符为对象的数据成员创建成员方式的副本。相对于此声明后的代码

b = a ;

对象将有两个指向同一动态分配内存的指针。因此,对于相同的内存地址,将调用delete运算符两次。

您必须明确写出类的复制赋值运算符和复制构造函数。

例如,复制赋值运算符可以采用以下方式

A & operator = ( const A &a )
{
    if ( this != &a )
    {
        char *tmp = new char[ std::strlen( a.name ) + 1 ];
        std::strcpy( tmp, a.name );

        delete [] this->name;

        this->_id = a._id;
        this->name = tmp;
    }

    return *this;
} 

本声明

b.name = "abc"

也错了。字符串文字具有静态存储持续时间。所以你可能不会删除他们的记忆。

答案 2 :(得分:0)

您将指针从实例a复制到实例b。

当实例a的析构函数运行时,它会删除内存。

当实例b的析构函数运行时,它会再次删除相同的内存。

您需要向此类添加副本和赋值构造函数。 (如果你正在使用c ++ 11,那么移动构造函数)

答案 3 :(得分:0)

  

我不明白为什么它说&#34; 0x10f9bcf64:被释放的指针是   未分配&#34;因为b.name显然是指向0x10f9bcf64和   不再与a重叠!

因为b的析构函数在静态字符串上调用delete []

  

我也想知道如何解决这个问题。

您应该已经定义了一个复制构造函数,例如:

A::A(const A& lhs) {
    _id = lhs.id;
    name = new char [ strlen (lhs.name) + 1 ] ;
    strcpy ( name, lhs.name ) ;
}

还制作了name_id private