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

The destructor is unable to delete a continuous memory block that has been allocated

以下代码可以编译但在 运行 时间内出现错误:

# 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: pointer being freed was not allocated

因为 b.name 显然指向 0x10f9bcf64,并且不再与 a 重叠!

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

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

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

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

您需要为此class 添加一个复制和赋值构造函数。 (如果您使用的是 c++11,还有移动构造函数)

I do not understand why it says "0x10f9bcf64: pointer being freed was not allocated" since b.name is obviously directed to 0x10f9bcf64 and does not overlap with a's any more!

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

I also wonder how this issue could be addressed.

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

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

并且还制作了name_idprivate

对于初学者来说,构造函数声明应该如下所示

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

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

默认的复制赋值运算符对对象的数据成员进行逐成员复制。相对于此语句后的代码

b = a ;

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

您必须为您的 class 显式编写复制赋值运算符和复制构造函数。

例如,复制赋值运算符可以如下所示

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"

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

您应该先阅读有关 Rule of 3/5/0 的内容。您的陈述:

b = a;

违反规则 3(如果您使用的是现代 C++,即 C++11 或更高版本,则为 5),因为您的 class 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规则的精彩世界。