将对象分配给自身导致奇怪的问题

Assigning object to itself cauing strange problems

我正在使用 C 标准库在 C++ 中处理字符串 class。

class 相当简单:构造函数获取一个字符串并使用 malloc 将它所需的内存量分配到一个 char 指针中,构造函数释放它,如果需要,它会重新分配。其余的是 reading/writing 个不需要更多或更少内存的函数。

我做的一个函数将内容更改为小写,将其放入一个新对象(在堆栈上创建)并 returns 它。但是,我尝试按照以下方式做一些事情:str = str.toLower(); 不用说,它失败得很惨。

每次我启动 exe 时它都会崩溃。经过数小时的盲目搜索后,我制作了消息框,告诉我何时分配和释放了某些东西以及它是什么。结果出于某种原因,它不断地从任何地方分配和取消分配各种项目。我假设这是由于悬空指针造成的,所以我修复了它。

但是,它仍在分配和释放一堆东西并崩溃。

相关代码如下:

Constructor/Destructor:

String::String(const char* str)
{
    data = nullptr;

    MessageBox(NULL, str, "Allocating", MB_OK);

    //getSizeOfCharArray does not include the NULL at the end, so add 1
    data_size = getSizeOfCharArray(str)+1;
    data = (char*)malloc(sizeof(char)*data_size);
    strcpy(data, str);
}

String::~String()
{
    MessageBox(NULL, data, "Freeing", MB_OK);

    if (data != nullptr)
    {
        free(data);
        data = nullptr;
    }
}

小写函数:

String String::toLower()
{
    char* lower = (char*)malloc(sizeof(char)*data_size);
    for (int i = 0; i < data_size; i++)
    {
        lower[i] = tolower(data[i]);
    }
    String temp(lower);
    free(lower);
    return temp;
}

主要:

int main()
{
    String str("String contents");

    str = str.toLower(); /* This line is what causes everything
                             to go wrong. But why? */
    cout << str.c_str() << endl; //c_str returns a const char* pointer

    //Alternatively, I can do this and it's fine.
    cout << str.toLower().c_str() << endl;
}

方框如下:

Allocating "String contents"

Allocating "string contents"

Freeing "string contents"

Allocating "/z" (actually some other character that looks like z)

Freeing "/z" (same strange character)

Allocating "/z" (same)

Freeing "/z" (they're all not z)

Allocating "/z"

Freeing "/z"

Allocating "/z"

Freeing "/z"

Allocating "/z"

Freeing "/z"

Allocating "/z"

Freeing "/z"

Allocating "/z"

Freeing "/z"

我有其他对话框文本,但显然“/z”是常见的。

这样持续了一段时间。

可能是什么原因造成的?

如果您没有 operator= 重载,将执行浅拷贝而不是深拷贝。我想如果你能粘贴 operator=.

就好了

您尝试的尝试毫无意义,但没关系。

在 C++ 中,如果您没有为您的 classes 显式声明赋值运算符重载,则会为您隐式创建一个执行浅拷贝的运算符。复制构造函数和默认构造函数和析构函数也是如此。任何其他用户定义的构造函数的存在将禁止为您隐式创建默认构造函数,因此如果除了采用 const char* 的构造函数之外您仍然希望它存在,您必须显式声明它。

在 classes 中使用动态内存时,我们有所谓的 3 法则或邪恶三部曲。这意味着您必须显式地编写一个 (1) 析构函数,否则您将面临内存泄漏的风险,并且您必须显式地编写或删除一个 (2) 复制构造函数和 (3) 复制赋值运算符,否则您将面临共享同一内存的风险多个实例,这至少会导致同一内存被删除不止一次,如果不是其他不良行为也是如此。

由于您没有post您的字符串 class 是什么样子,下面是您可能需要做的示例:

class String
{
public:
    String();    // Default constructor
    String(const char* s);    // Custom constructor
    String(const String& rhs);    // Copy constructor
    String& operator=(const String& rhs);    // Copy assignment operator
    ~String();    // Destructor
    String(String&& rhs);    // (Optional) Move constructor
    String& operator=(String&& rhs);    // (Optional) Move assignment

    // TODO: Other public functions

private:
    char* m_string;
};

String::String() : m_string(nullptr)
{
}
String::String(const char* s)
{
    // TODO: Copy the string into m_string
}
String::String(const String& rhs)
{
    // TODO: Deep copy
}
String& String::operator=(const String& rhs)
{
    if(this != &rhs)
    {
        // TODO: Deep copy
    }
    return *this;
}
String::~String()
{
    delete m_string;
}

此外,这让我很烦,您不需要使用 Windows 消息框作为记录器。只需将日志消息输出到控制台或日志文件。

您的字符串 class 需要一个有效的、用户定义的复制构造函数和赋值运算符。

由于函数 toLower returns 是 String 的值,因此对象需要正确的复制语义,因为 operator= 对于 String class 被调用。

如果没有用户定义的复制构造函数,将使用编译器的默认复制构造函数。默认版本只是将指针值复制到新的 String 对象——它不知道如何调用 malloc,然后 strcpy 来复制数据。你必须自己写这个。

String::String(const String& str) : data_size(str.data_size),   
                                    data(malloc(str.data_size))
{  strcpy(data, str.data); }

赋值运算符也需要完成。它应该看起来像这样:

#include <algorithm>
//...
String& operator=(String str) 
{  
    std::swap(data, str.data);
    std::swap(data_size, str.data_size);
    return *this;
}

所有这些都在下面的 link 中解释:

What is The Rule of Three?

此外,对于 C++,您使用 new[]delete[] 而不是 mallocfree