析构函数导致分段错误

时间:2012-02-18 00:44:13

标签: c++

我已经实现了一个类字符串,类似于std :: string one。 调用析构函数时遇到问题:字段长度具有字段中分配的字符长度。 这是班级:

class indexException:public std::exception
{
    public:
    virtual const char* what()
    {
        return "Index is either too long, or negative";
    }
};

class string
{
    public:
    static const unsigned int length_max=100;
    string(const char* field=NULL)
    {
        if(field!=NULL)
        {
            const unsigned int length=strlen(field);
            this->field=new char[length+1];
            this->length=length;
            for(unsigned int i=0;i<=length;i++)
                this->field[i]=field[i];
        }
        else
        {
            this->field=NULL;
            length=0;
        }
    }
    string(string& str)
    {
        string(str.field);
    }
    ~string()
    {
        if(length>0)
            delete field;
    }
    char& operator[] (int i) const throw()
    {
        try
        {
            if(i<0 || i>=(int)length)
                throw indexException();
        }
        catch(indexException& e)
        {
            std::cerr << e.what() << std::endl;
        }
        return field[i];
    }
    string& operator=(const char* field)
    {
        const unsigned int length=strlen(field);
        if(this->length>0)
            delete this->field;
        this->field=new char[length];
        this->length=length;
        for(unsigned int i=0;i<length;i++)
            this->field[i]=field[i];
        return *this;
    }
    string& operator= (const string& str)
    {
        if(this!=&str)
            *this=str.field;
        return *this;
    }
    operator char* ()
    {
        return field;
    }
    friend std::ostream& operator<< (std::ostream& out, string& str);
    friend std::istream& operator>> (std::istream& in, string& str);
    public:
    unsigned int length;
    char* field;
};

std::ostream& operator<<(std::ostream& out, string& str)
{
    out << str.field;
    return out;
}

std::istream& operator>> (std::istream& in, string& str)
{
    char temp[string::length_max];
    in >> temp;
    str=temp;
    return in;
}

如果我使用赋值运算符,则不会导致分段错误。 但它直接造成它。 我解释一下:

int main(int argc,char** argv)
{
    string str="hi";
    string str2=str;
    return 0;
}

在赋值运算符重载中设置断点,我意识到赋值运算符不会导致分段错误。 问题出在后面,当从主要退出时。 如果我删除析构函数我没有得到这个分段错误,但我知道为什么我会遇到这个问题。

编辑:我已经明白问题出在哪里了。 我按照你的建议,但它仍然是分段错误。 但现在它不再在析构函数方法上崩溃,而是在赋值运算符重载:

    string& operator=(const char* field)
    {
        unsigned int length=0;
        if(field!=NULL)
            length=strlen(field);
        else
            field="";
        if(this->length>0)
            delete[] this->field;
        this->field=new char[length+1];
        this->length=length;
        strcpy(this->field,field);
        return *this;
    }

问题是当我删除this-&gt;字段时,调试器停在那里。 分段错误的一个例子:

string str("hi");
string str2=str;

这会导致分段错误。我想这是因为str2没有初始化,并且length有一个未定义的值。 如果我这样做:

string str("hi");
string str2;
str2=str;

没有任何分段错误。为什么? 我以为这叫:

string str2;

还调用构造函数,还是“=”运算符具有优先权? 怎么解决这个问题?

PS:我也改变了其他的东西,比如复制构造函数。 完整代码在这里: http://pastebin.com/ubRgaVr8

解决:我按照接受的回复中的建议更改了复制构造函数:

    string(const string& str)
    {
        length=str.length;
        field=new char[str.length+1];
        memcpy(field,str.field,length+1);
    }

4 个答案:

答案 0 :(得分:3)

您的复制构造函数不会初始化对象。

string(string& str)
{
    string(str.field); // Does nothing
}

string(str.field)创建一个未命名的 string并立即抛弃它。 使用不同的构造函数初始化此对象。

由于您的对象现在只包含随机性,因此当您尝试销毁它时会发生不好的事情。

要确保事情已初始化,请创建一个私有成员函数

void initializeFromChars(const char* cString);

完成工作并在构造函数和赋值运算符中使用它。

答案 1 :(得分:2)

使用

分配内存后
field = new char[length+1];

您应该删除它:

 delete [] field;

而且你没有检查你的分配是否成功。

另一个被认为是良好做法的事情是在删除后将field设置为NULL,以便它不会被删除两次(如果你开始提供类),例如:

~string(){
    delete [] field;
    // field = NULL;
}

注意:根据Dietmar Kühl设置field=NULL不是一个好习惯(看看评论)并选择你的方式,这里有一个具体的问题:{ {3}}。

注意2 Is it worth setting pointers to NULL in a destructor?指出delete [] field如果指针为NULL则无效,且不需要整个条件。

string& operator=(const char* field)中你可能想要分配length + 1并迭代它(包括终止NULL)。

我不喜欢你的string& operator= (const string& str),你已经缓存了关于字符串长度的信息,你使用的是strlen(),而不是手工复制char的字符。

您的复制构造函数看起来也很糟糕......您应该“复制”手动分配并逐字节复制到它。或者更确切地说,像fromCString(const char *)一样构建受保护的函数,并在构造函数和赋值运算符中使用它。

如果这些没有帮助请求评论以获得更多帮助。

答案 2 :(得分:2)

您的析构函数使用delete时应使用delete[]

答案 3 :(得分:2)

编辑:废弃我之前的回答,因为它不正确。

问题似乎是复制构造函数,您从源实例传递字段,好像它只是另一个空终止的char *,但它不是。

在前一个语句调用的char *赋值期间,不要复制空字符,而是使用内部长度字段,并仅复制那么多字节。

所以你的拷贝构造函数应该是:

string(string& str)
{
   length = str.length;
   field = new char[length];
   memcpy(field, str.field, length);
}

或者,如果你想保留与null终止函数的兼容性,并确保为所有其他赋值/构造函数保留null,等等:

string(string& str)
{
   length = str.length;
   field = new char[length + 1];
   memcpy(field, str.field, length + 1);
}

事实上,混合空终止,并且在整个班级中指定的长度字符串似乎让你感到困惑。

我将创建一个内部的,私有的,单一的处理方法,以及一组设置各种源类型的方法,并让构造函数,赋值运算符和析构函数使用它们。

这样,您只有一个地方可以进行任何给定的操作,而不是在相同的功能上处理许多微小的变化。例如:

private:
    void internalSet(const char *source) {
        if (source == NULL) {
            length = 0;
            field = NULL;
        }else{
            length = strlen(source);
            field = new char[length];
            memcpy(field, source, length);
        }
    }

    void internalSet(const string &source) {
        length = source.length;
        if (length > 0) {
            field = new char[length];
            memcpy(field, source.field, length);
        }else{
            field = NULL;
        }
    }

    void internalDispose() {
        delete[] field;
    }

public:
    string() : field(NULL), length(0) {}

    string(const string& source) { internalSet(source); }
    string(const char *source) { internalSet(source); }

    ~string() { internalDispose(); }

    string& operator=(const char *source) {
        internalDispose();
        internalSet(source);
        return *this;
    }

    string& operator=(const string &source) {
        internalDispose();
        internalSet(source);
        return *this;
    }

    void clear() {
        internalDispose();
        length = 0;
    }