【问题标题】:C++: pointer being freed was not allocatedC ++:未分配被释放的指针
【发布时间】:2014-09-11 17:54:12
【问题描述】:

我正在清理我为一个使用 XCode 5 的课程编写的玩具程序。部分任务是熟悉动态内存分配(这意味着它必须使用 newdelete尽管没有它它真的可以正常工作)。我正在接收:

HeapOfStudents(67683,0x7fff769a6310) malloc: *** error for object 0x100300060: pointer being freed was not allocated *** set a breakpoint in malloc_error_break to debug

发生错误:

Student::~Student() {
    delete firstName; // <- on this line
    delete lastName;
    delete address;
    delete birthDate;
    delete gradDate;
    delete gpa;
    delete crHours;
}

正在调用的...

int main() {

    string line = "";
    vector<Student> students;


    //
    //Create new students from file
    //
    ifstream data_file ("heapData");

    if (data_file.is_open()) {

        while(getline(data_file, line))
            students.push_back(*new Student(line));  <- inside this call here

        data_file.close();
    }

    else return 0;

完整的Student 类如下。

重要的是,我注意到虽然每个line 都被正确推送到向量,但当下一个Student 被推送时,前一个Student inside 向量被破坏。我总是在循环的第三次迭代中看到malloc 错误(文件有 50 行)。

如果我将vector&lt;Student&gt; 更改为vector&lt;Student *&gt; 并将student.push_back(*new Student(line)) 更改为student.push_back(new Student(line)),程序将无异常运行。

那么我的问题是:有人可以解释一下,在较低级别上,在这个循环中发生了什么会导致这个错误吗?

我的猜测是 *new Student(line) 每次都使用相同的指针,因此在每次迭代中,数据都会因为被释放而损坏(尽管这种解释引发了问题),而 new Student 每次都返回一个新指针,保留传递给students 的数据。我在想,也许我需要写一个拷贝构造函数……这只是我的猜测。

这是Student 类。 AddressDate 也是非常相似的自定义类。我没有包括它们,因为它们似乎不是问题的一部分。

//Constructors
Student::Student() {

    firstName = new string("Testy");
    lastName = new string("Testerson");
    address = new Address("000 Street St", "", "Citytown", "State", "00000");
    birthDate = new Date("01/02/9876");
    gradDate = new Date("01/02/6789");
    gpa = new string("10.0");
    crHours = new string("300");

}

Student::Student(string FN, string LN, string L1, string L2, string C, string S, string Z, string BD, string GD, string GPA, string Hr) {

    firstName = new string(FN);
    lastName = new string(LN);
    address = new Address(L1, L2, C, S, Z);
    birthDate = new Date(BD);
    gradDate = new Date(GD);
    gpa = new string(GPA);
    crHours = new string(Hr);

}

Student::Student(string line) {
    set(line);
}

//Destructors
Student::~Student() {
    delete firstName;
    delete lastName;
    delete address;
    delete birthDate;
    delete gradDate;
    delete gpa;
    delete crHours;
}

//Member Functions
void Student::set(string line) {

    firstName = new string(line.substr(0, line.find_first_of(",")));
    line = line.substr(line.find_first_of(",") + 1, string::npos);

    lastName = new string(line.substr(0, line.find_first_of(",")));
    line = line.substr(line.find_first_of(",") + 1, string::npos);

    address = new Address();

    address->setLine1(line.substr(0, line.find_first_of(",")));
    line = line.substr(line.find_first_of(",") + 1, string::npos);

    address->setLine2(line.substr(0, line.find_first_of(",")));
    line = line.substr(line.find_first_of(",") + 1, string::npos);

    address->setCity(line.substr(0, line.find_first_of(",")));
    line = line.substr(line.find_first_of(",") + 1, string::npos);

    address->setState(line.substr(0, line.find_first_of(",")));
    line = line.substr(line.find_first_of(",") + 1, string::npos);

    address->setZip(line.substr(0, line.find_first_of(",")));
    line = line.substr(line.find_first_of(",") + 1, string::npos);

    birthDate = new Date(line.substr(0, line.find_first_of(",")));
    line = line.substr(line.find_first_of(",") + 1, string::npos);

    gradDate = new Date(line.substr(0, line.find_first_of(",")));
    line = line.substr(line.find_first_of(",") + 1, string::npos);

    gpa = new string(line.substr(0, line.find_first_of(",")));
    line = line.substr(line.find_first_of(",") + 1, string::npos);

    crHours = new string(line.substr(0, line.find_first_of(",")));
    line = line.substr(line.find_first_of(",") + 1, string::npos);

}

void Student::printReport() {

    cout << *lastName << ", " << *firstName << ", " << address->getAddress()
    << ", " << birthDate->getDate() << ", " << gradDate->getDate()
    << ", " << *gpa << ", " << *crHours << endl;

}

void Student::printName() {
    cout << *lastName << ", " << *firstName << endl;
}

string Student::getName() {
    return string(*lastName + ", " + *firstName);

编辑以下是我为学生班编写的复制构造函数和赋值运算符,如果有人有兴趣查看/批评它们:

Student::Student(const Student & student){

    firstName = new string(*student.firstName);
    lastName = new string(*student.lastName);
    address = new Address(*student.address);
    birthDate = new Date(*student.birthDate);
    gradDate = new Date(*student.gradDate);
    gpa = new string(*student.gpa);
    crHours = new string(*student.crHours);

}

Student& Student::operator=(const Student & student) {
    return *new Student(student);
}

【问题讨论】:

  • 你为什么还要在这里使用指针?只需将firstName 设为string 而不是string *
  • @cdhowie:见第一段。这是一个学习内存管理的练习。
  • 在现代 C++ 中没有裸指针。
  • 这段代码片段*new Student(line)本质上是内存泄漏! new 分配堆空间并返回一个(临时)指向它的指针。 * 运算符取消引用访问 Student 对象的指针。此时,您不再需要在 delete 语句中使用指针..
  • Rule of Three 违规。

标签: c++ pointers vector


【解决方案1】:

您似乎没有关注Rule of Three,因此如果您复制Student 对象,将会发生坏事。具体来说,两者都将包含指向相同对象的指针,它们都将尝试删除 - 所以一个会尝试删除另一个已经删除的东西。

赋予类有效复制语义的最简单方法是禁止复制,方法是删除复制构造函数和复制赋值运算符:

class Student {
    Student(Student const &) = delete;
    void operator=(Student const &) = delete;

    // rest of class...
};

否则,如果您希望该类是可复制的,请实现这些以做一些明智的事情。

此外,这会导致内存泄漏:

students.push_back(*new Student(line));

通过动态创建Student,将其复制到向量中,并丢弃指向动态对象的唯一指针。如果要存储对象,则推送一个临时副本:

students.push_back(Student(line));

或者,您可以将容器类型更改为vector&lt;Student*&gt;,并记住在删除其指针时删除每个对象。 (正如您所说,作为指针争论的练习,这是一件合理的事情,但不要在您想要维护的任何程序中这样做。)

一旦您了解了手动内存管理的可怕细节,除非绝对必要,否则请避免动态分配,让自己的生活更轻松,当您确实需要时,始终使用智能指针、容器和其他方式管理它@987654322 @ 类型,而不是通过处理原始指针。

【讨论】:

  • 我认为三法则已经用 C++11 更新为五法则 :-)
  • @user515430:不是这样。您有时需要特殊的移动语义来提高效率,但三法则足以保证正确性。
  • 您在三法则上的链接指向您的个人资料,这样您就知道了。 ;)
  • @E_net4:哎呀,打开的标签太多了。现在应该去正确的地方了。
  • 所以,我的直觉是我需要一个复制构造函数。从我在此页面上看到Rule of Three 的次数来看,看来我是对的。我知道 vector::push_back() 会制作一个要推送的副本。谢谢!
【解决方案2】:

这个问题很可能发生,因为默认的复制和赋值运算符没有做正确的事情——它们复制了指针。因此,如果您在某处创建一个临时的Student,您将在该临时被破坏时删除指针,而原始指针仍指向相同的(现已删除)对象。当它被破坏时,它会尝试删除已经被删除的指针,因此会出错。

您需要遵循rule of three:如果您需要自定义析构函数、赋值运算符或复制构造函数,则需要所有这些。

将以下公共成员添加到您的班级:

Student(Student const &);
Student & operator=(Student const &);

并像这样定义它们:

Student::Student(Student const & other)
{
    firstName = new string(other.firstName);
    // And so on, for each pointer member.
}

Student & Student::operator=(Student const & other)
{
    *firstName = other.firstName;
    // And so on, for each pointer member.

    return *this;
}

请注意,所有这些都可以通过使用string firstName; 而不是string * firstName; 来避免——在这种情况下,默认析构函数、复制构造函数和赋值运算符会做正确的事情,您不需要定义任何一个。

此外,请注意您的 AddressDate 类可能有同样的问题!每当你使用原始指针作为类成员时,你都必须做这些体操。

【讨论】:

    【解决方案3】:

    您遇到的一个问题是您保留了一个Student 对象向量,而不是一个指向动态分配对象的指针向量(因此是Student* 类型)。

    只需将您的 students 变量替换为 std::vector&lt;Student*&gt; students;。从那时起,您可以简单地push_backnew 创建的指针。

    问题是vector 已经处理了内存分配,因此进行推送的行(您在代码中突出显示)正在将 Student 复制到向量中的某个位置。之后,指向动态分配对象的指针将变得无法访问。

    正如 Barmar 所指出的,拥有一个指针向量还具有能够推送Student 子类的指针的优势。一个简单的例子:

    class PhDStudent : public Student { ... }
    
    students.push_back(new PhDStudent(...));
    

    此外,您还应该在课堂上考虑许多其他调整:

    • 您的构造函数按值获取string 参数,这意味着它们是从其来源深度复制的。这里最好使用const string&amp;,以避免不必要的复制。

    • 正如其他一些答案(Mike Seymour、cdhowie、Anton Savin 以及任何将要指出这一点的人)已经指出的那样,您应该关注Rule of Three。如果您希望您的类是可复制的,您还应该实现复制构造函数和复制赋值运算符。如果您使用的是 C++11,您还可以利用移动构造函数和移动赋值,以减少移动这些对象时的分配次数。

    【讨论】:

    • 使用指针还有一个额外的好处,即您还可以将Student 的子类推送到向量上,并利用虚函数。
    • 所以我认为我没有意识到/不知道/无论什么是 vector::push_back() 的机制。现在很明显它正在复制正在传递的内容。因此我需要关注Rule of Three
    • 就是这样。由于没有正确制作副本,因此该程序后来被谴责有问题。
    【解决方案4】:

    您可能不想复制Students,但仍想将它们放入vector。在 C++11 中,只需添加一个移动构造函数即可解决:

    class Student {
    public:
        Student() { ...}
        ~Student() {...}
        Student(const Student&) = delete; // optional: will be deleted implicitly
    
        Student(Student&& other) {
            firstName = other.firstName;
            other.firstName = nullptr;
            // ...
        }
    };
    

    这样复制构造函数和赋值运算符将被隐式删除,因此您将无法进行复制。但是vector 和其他容器将使用移动构造函数就好了。这当然对Student 的使用设置了一定的限制。

    vector<Student> students;
    students.push_back(Student()); // OK, student moves
    Student s;
    students.push_back(s); // Error: copy constructor for Student is deleted
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-03-11
      • 1970-01-01
      • 1970-01-01
      • 2014-06-07
      • 1970-01-01
      • 2018-09-30
      • 2016-08-30
      相关资源
      最近更新 更多