在程序中获取调试断言错误

时间:2014-04-05 21:10:52

标签: c++

我只是想创建一个简单的程序来练习一些C ++,但我不确定为什么我得到这个当前的错误。输出提供了我想要的结果,但在成功输出后,我不断收到调试断言错误。它是内存泄漏还是什么?我不知道它可能是什么。

部首:

#include <iostream>
class Record {
    char* rec;
public:
    Record();
    Record(const char*);
    Record(const Record&);
    ~Record();
    void display(std::ostream&);
    Record& operator=(const char*);
};

std::ostream& operator<<(std::ostream& os, Record& r);

CPP:

#define _CRT_SECURE_NO_WARNINGS
#include "Record.h"

Record::Record() {
    rec = nullptr;
}

Record::Record(const char* s) {
    if(s != nullptr) {
    rec = new char[strlen(s) + 1];
    strcpy(rec, s);
    } else {
    *this = Record();
    }
}

Record::Record(const Record& r) {
    *this = r;
}

Record::~Record() {
    delete [] rec;
}

void Record::display(std::ostream& os) {
    os << rec;
}

Record& Record::operator=(const char* s) {
    if (rec != s)
    delete [] rec;
    if(s != nullptr) {
    rec = new char[strlen(s) + 1];
    strcpy(rec, s);
    } 
    else {
    rec = nullptr;
    }
    return *this;
}

std::ostream& operator<<(std::ostream& os, Record& r) {
    r.display(os);
    return os;
}

主:

#include <iostream>
#include "Record.h"
using namespace std;

int main() {
    Record rec1("inheritance"), rec2 = rec1;

    cout << rec1 << endl;
    cout << rec2 << endl;
    rec1 = "overloading";
    cout << rec1 << endl;
    rec2 = rec1;
    cout << rec2 << endl;

    return 0;
}

3 个答案:

答案 0 :(得分:1)

我会把它作为一个答案,因为它对你的班级表现如何很重要,并且结论是“一切正常”#34;是C ++不是那么容易用语言的原因之一。

你编写的main()程序没有测试非常简单的东西。看这里:

int main() {
   Record rec1("inheritance");
   Record rec2 = rec1;
}

如果您调试此代码,您将看到为rec2 = rec1行调用此函数:

Record::Record(const Record& r) {
    *this = r;
}

好的,所以调用了复制构造函数。但是这行代码做了什么?:

*this = r;

调用您编写的带有const char *的赋值运算符。相反,它调用默认的赋值运算符来记录&amp;,问题是 - 你没有写一个。所以最后发生的事情是编译器生成的赋值运算符被调用,它执行浅拷贝。

在main()程序中,当main()返回时,rec2和rec1都将调用它们各自的析构函数。问题是rec2将删除指针值,ok,但是rec1将删除相同的指针值(没有好处),导致堆损坏。我使用Visual Studio 2013运行代码,并在main()返回时立即弹出一个断言对话框。

因此,您需要编写一个带有此签名的用户定义赋值运算符:

Record& Record::operator=(const Record&)

答案 1 :(得分:0)

不要从*this =和复制构造函数调用赋值运算符const char*。这通常是不好的做法。在您的情况下,因为您还没有定义一个带const Record&的赋值运算符,所以会调用默认赋值运算符,它只是复制指针并执行浅复制,意味着两个Record将在rec s中具有相同的指针 - 这在PaulMcKenzie's answer中已经指出并详细描述。但是,即使您确实定义了赋值运算符,如果您执行与现有赋值运算符相同的操作,则成员变量rec将未初始化,delete会导致未定义的行为。

请参阅Calling assignment operator in copy constructor进行讨论,以及您可以做什么,而不是从构造函数中调用赋值运算符。

修改

使程序运行的一种方法是让构造函数看起来像这样。

void Record::InitFrom(const char* s)
{
    if(s != nullptr) {
        rec = new char[strlen(s) + 1];
        strcpy(rec, s);
    } else {
        rec = nullptr;
    }
}

Record::Record(const char* s) {
    InitFrom(s);
}

Record::Record(const Record& r) {
    InitFrom(r.s);
}

您还可以将新的InitFrom方法合并到赋值运算符中。

Record& Record::operator=(const char* s) {
    if (rec != s) { // this test only really necessary if assigning from Record
        delete [] rec;
        InitFrom(s);
    }
    return *this;
}

您也可能拥有一个const Record&的赋值运算符。你需要析构函数。

答案 2 :(得分:0)

如果没有看到您初始化记录的值,很难确定是什么。根据您对程序行为和代码的描述,我可以想到一个可能的解释,尽管可能还有其他的。

我的猜测是std :: ostream将char *像c字符串一样处理,它需要一个以\ 0结尾的字符序列。如果你的Record已经用一系列没有以\ 0结尾的字符进行初始化,那么它将继续进行指针递增,一次流出一个字符,直到它到达无效的内存部分。这将导致未定义的行为,这可能会在您的标准实现(即您正在使用的编译器)中触发调试断言。

我向你提供这个猜测,因为你说这是一个学习exersize,所以我不会质疑你的课堂设计,但试着帮助你了解发生了什么。然而,其他地方的评论非常相关。如果您有记录存储std :: string而不是字符数组,则不会发生此问题(以及其他一些可能的问题)。当然,这个答案可能无法帮助你学习你想要学习的知识。