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

Getting debug assertion errors in program

本文关键字:断言 错误 调试 获取 程序      更新时间:2023-10-16

我只是想创建一个简单的程序来练习一些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;
}

我会把这个作为一个答案,因为它对你的类的行为很重要,并且得出"一切都在工作"的结论是C++语言不那么容易的原因之一。

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

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

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

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

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

*this = r;

不会调用您编写的采用 const char* 的赋值运算符。 相反,它调用采用 Record&的默认赋值运算符,问题是 - 你没有写一个。 因此,最终发生的是调用编译器生成的赋值运算符,该运算符执行浅层复制。

在 main() 程序中,当 main() 返回时,rec2 和 rec1 都将调用各自的析构函数。 问题是 rec2 将删除指针值,好吧,但随后 rec1 将删除相同的指针值(不好),从而导致堆损坏。 我用Visual Studio 2013运行了你的代码,当main()返回时,立即弹出了一个断言对话框。

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

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

不要从const char*调用赋值运算符 *this = 并复制构造函数。这通常是不好的做法。在你的例子中,因为你没有定义一个接受const Record&赋值运算符,所以调用默认赋值运算符,它只是复制指针并执行浅拷贝,这意味着两个Record在它们的rec中都有相同的指针——这在 PaulMcKenzie 的回答中被指出并详细描述。但是,即使您确实定义了该赋值运算符,如果您执行与现有赋值运算符相同的操作,则成员变量rec将未初始化,并且delete它会导致未定义的行为。

有关讨论,请参阅在复制构造函数中调用赋值运算符,以及除了从构造函数调用赋值运算符之外可以执行哪些操作。

编辑

使

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

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&的赋值运算符。您确实需要析构函数。

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

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

当你说这是一个学习练习时,我给你这个猜测,所以我不质疑你的课堂设计,但试图帮助你理解发生了什么。 然而,其他地方的评论是相当相关的。 如果您有记录存储 std::string 而不是字符数组,则不会出现此问题 (以及其他几个可能的问题)。 当然,这个答案可能不会帮助你学习你想要学习的东西。