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

时间:2015-01-29 13:00:54

标签: c++ memory memory-management

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

该类非常简单:构造函数接受一个字符串并使用malloc将其所需的内存量分配到char指针中,构造函数释放它,如果需要,它会重新分配。其余的是读/写功能,不需要更多或更少的内存。

我做的一个函数将内容更改为小写,将其放入一个新对象(在堆栈上创建)并返回它。但是,我尝试了以下几点: str = str.toLower(); 并且不用说,它失败了。

每次我启动exe它都会崩溃。在无意识地搜索了几个小时之后,我制作了消息框告诉我何时分配和释放了什么以及它是什么。由于某种原因,它不断分配和解除分配各种项目。我假设它是由于悬空指针引起的,所以我解决了这个问题。

但是,它仍在分配和释放大量内容并崩溃。

以下是相关代码:

构造/析构:

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"

我有其他对话框文字,但显然是&#34; / z&#34;是一个常见的。

它有点像这样。

可能导致这种情况的原因是什么?

3 个答案:

答案 0 :(得分:1)

如果您没有operator=重载,则会执行浅拷贝而不是深拷贝。我认为如果您可以粘贴operator=

会很棒

答案 1 :(得分:0)

你尝试的东西没有意义尝试,但没关系。

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

在课堂上使用动态记忆时,我们会喜欢称之为3的规则或邪恶的三部曲。这意味着您必须显式编写(1)析构函数或冒泄漏内存的风险,并且必须显式写入或删除(2)复制构造函数和(3)复制赋值运算符,否则您可能会冒险共享相同的内存多个实例,如果没有其他不良行为,至少会导致同一个内存被删除多次。

由于您没有发布String类的内容,下面是您可能需要做的示例:

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消息框作为记录器。只需将日志消息输出到控制台或日志文件即可。

答案 2 :(得分:0)

您的字符串类需要一个工作的,用户定义的复制构造函数和赋值运算符。

由于函数toLower按值返回String,因此对象需要正确的复制语义,因为调用了operator=String

如果没有用户定义的复制构造函数,将使用编译器的默认复制构造函数。默认版本仅将指针值复制到新的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;
}

所有这些都在以下链接中解释:

What is The Rule of Three?

此外,对于C ++,您使用new[]delete[]代替mallocfree