簡體   English   中英

析構函數導致分段故障

[英]Destructor causing segmentation fault

我實現了一個類字符串,類似於std :: string。 調用析構函數時,我遇到了一個問題:字段長度具有在字段中分配的字符的長度。 這是課程:

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;
}

將斷點放入賦值運算符的重載中,我意識到分配運算符不會引起分段錯誤。 問題出在從main退出時。 如果刪除析構函數,我不會遇到此分段錯誤,但是我會知道為什么會出現此問題。

編輯:我知道哪里出了問題。 我遵循了您的建議,但仍然會導致細分錯誤。 但是現在,它不再在析構函數方法上崩潰,而是在賦值運算符重載上崩潰:

    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-> field時,調試器在那里停止。 分段錯誤的示例:

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);
    }

您的副本構造函數不會初始化該對象。

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

string(str.field)創建一個未命名的 string並立即將其丟棄。 使用不同的構造函數初始化這個對象。

由於您的對象現在僅由隨機性組成,因此當您嘗試破壞它時,會發生壞事。

為確保初始化,請設置私有成員函數

void initializeFromChars(const char* cString);

完成工作並在構造函數和賦值運算符中使用它。

一旦你分配了內存

field = new char[length+1];

您應該使用以下命令將其刪除:

 delete [] field;

並且您不檢查分配是否成功。

被認為是良好做法的另一件事是,在刪除后將field設置為NULL ,這樣就不會被刪除兩次(如果您開始提供類),例如:

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

注意 :根據DietmarKühl的設置,設置field=NULL並不是一種好習慣(請看注釋)並選擇您的方式,這關於此的具體問題: 是否值得在析構函數中將指針設置為NULL?

注2KerrekSB指出,如果指針為NULL並且不需要整個條件,則delete [] field將不執行任何操作。

比在string& operator=(const char* field)您可能想要分配length + 1並對其進行迭代(包括終止NULL )。

而且我不喜歡您的string& operator= (const string& str) ,您已緩存了有關字符串長度的信息,並且您正在使用strlen()而不是逐字符手動復制char。

您的復制構造函數看起來也很糟糕...您應該“復制”手動分配,然后逐字節復制到它。 或更確切地說,構建諸如fromCString(const char *)類的受保護函數,並在構造函數和賦值運算符中使用它。

如果這些方法不起作用,請在評論中尋求更多幫助。

您的析構函數應使用delete ,而應使用delete[]

編輯:取消了我以前的答案,因為它是不正確的。

問題似乎出在復制構造函數上,您正在從源實例傳遞字段,就好像它只是另一個以null終止的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);
}

實際上,在整個類中,混合null終止和指定長度的字符串如此之多,似乎使您感到困惑。

我將創建一個內部的,私有的,單一處理方法以及一系列方法來設置各種源類型,並讓構造函數,賦值運算符和析構函數代替使用它們。

這樣,您只有一個地方可以進行任何給定的操作,而不是在同一功能上處理許多細微的變化。 例如:

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;
    }

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM