在Visual Studio 2015中调用析构函数时奇怪的堆栈溢出

时间:2016-09-02 01:55:19

标签: c++ visual-studio-2015 heap-corruption

我在cpp中编写了一个简单的字符串类,但是当我添加一些新功能时,发生了一些不好的事情。当调用对象 s3 析构函数时,堆栈溢出。所以我花了一个小时来修复它,但我没有发现我的代码在本地出错,而且语法是正确的。因此,我要求我的朋友重新编码以帮助我修复它,但是当他在Mac上重新编码时。他的计划中没有发生任何不好的事情。所以我真的很担心它并且需要帮助解决它,如果我的代码真的有问题。如果没有,也许我应该对问题Visual Studio 2015发表评论。:(

这是MySTLString.h

class MySTLString
{
public:
    // Default constructor
    MySTLString();                                  

    // Constructs the string with count copies of character ch. 
    MySTLString(size_t count, char ch);     

    // Convert char consequence to MySTLString
    MySTLString(const char *s);

    // Copy constructor 
    MySTLString(const MySTLString &s);

    // Destructor
    ~MySTLString();


    using size_type = size_t;       
    using CharT = char;                         // the character type
    using value_type = char;                    // the value_type
    using reference = value_type;               // reference 
    using const_reference = const value_type&;  // const reference

    // Returns reference to the character at specified location pos.
    reference at(size_type pos);
//  const_reference at(size_type pos) const;    // I do not know how to convert a reference to const_reference when to return 

    // Returns reference to the first character
    CharT& front();
    const CharT& front() const;

    // Returns reference to the last character, equivalent to operator[](size() - 1)
    CharT& back();
    const CharT& back() const;

    // Returns pointer to the underlying array serving as character storage.
    CharT* data();
    const CharT* data() const;

    // Operator assignment overloaded
    MySTLString& operator=(const MySTLString &s);

    // Operator [] overloaded
    reference operator[](size_type pos);
    const_reference operator[](size_type pos) const;

private:
    char* data_;
    int length_;
};

这是MySTLString.cpp

#include "MySTLString.h"
#include <cstring>
#include <iostream>
#include <stdexcept>

// Construct a empty string(zero size and unspecified capacity).
MySTLString::MySTLString():data_(nullptr), length_(0)
{

}

// Constructs the string with count copies of character ch.
MySTLString::MySTLString(size_t count, char ch)
{
    length_ = count;
    if (count == 0)
    {   
        data_ = nullptr;
        return;
    }
    else    // when count is not 0
    {
        data_ = new char[length_];
        for (size_t i = 0; i < count; ++i)
        {
            data_[i] = ch;
        }
    }

}

// Constructs the string with contents initialized 
// with a copy of the null-terminated character string pointed to by s.
// The length of the string is determined by the first null character. 
MySTLString::MySTLString(const char *s)
{
    length_ = strlen(s);
    if (length_ == 0)
    {
        data_ = nullptr;
        return;
    }
    data_ = new char[length_];
    strcpy(data_, s);
}

// Copy constructor. 
// Constructs the string with the copy of the contents of other.
MySTLString::MySTLString(const MySTLString &s)
{
    if (s.data_ == nullptr)
    {
        length_ = 0;
        data_ = nullptr;
    }
    else
    {
        length_ = strlen(s.data_);
        data_ = new char[length_];
        strcpy(data_, s.data_);
    }
}

// Destructor
// Free data_ pointer memory
MySTLString::~MySTLString()
{
    if (data_ != nullptr)
    {
        delete []data_;
    }
    std::cout << "length_ = " << length_ << std::endl;  // for test
}

// Returns a reference to the character at specified location pos.
// Bounds checking is performed, exception of type std::out_of_range will be thrown on invalid acess
MySTLString::reference MySTLString::at(size_type pos)
{
    if (pos >= strlen(data_))
    {
        throw std::out_of_range("pos is cross-border!\n");
    }
    return data_[pos];
}


// Returns reference to the first character
MySTLString::CharT& MySTLString::front()
{
    if (data_ == nullptr)
    {
        throw std::out_of_range("String is empty!\n");
    }
        return data_[0];
}

const MySTLString::CharT& MySTLString::front() const
{
    return this->front();
}

// Returns reference to the last character
MySTLString::CharT& MySTLString::back()
{
    if (data_ == nullptr)
    {
        throw std::out_of_range("String is empty!\n");
    }
    return data_[0];
}

const MySTLString::CharT& MySTLString::back() const
{
    return this->back();
}

// Returns pointer to the underlying array serving as character storage.
// The pointer is such that the range[data(); data()+strlen(data_)] is valid 
// in it correspond to the values stored in the string
MySTLString::CharT* MySTLString::data() 
{
    if (data_ == nullptr)
    {
        throw std::out_of_range("String is empty!\n");
    }
    return data_;
}

const MySTLString::CharT* MySTLString::data() const
{
    return this->data();
}

// Operator= overloaded
// Replace the contents with a copy of str.
// If *this and str are the same object, this function has no effect
MySTLString& MySTLString::operator=(const MySTLString &s)
{
    // If *this and str are the same object, this function return *this
    if (this == &s)
    {
        return *this;
    }
    if (s.length_ == 0)
    {
        length_ = 0;
        data_ = nullptr;
        return *this;
    }
    char* temp = s.data_;   // copy *s.data_
    delete data_;                       // free old memory
    data_ = new char[s.length_];            // copy data to data_ member
    strcpy(data_, temp);
    length_ = s.length_;
    return *this;                       // return this object
}

// Operator[] overloaded
// Returns a reference to the character at specified location pos. 
// No bounds checking is perfromed.
MySTLString::reference MySTLString::operator[](size_type pos)
{
    return this->at(pos);
}

MySTLString::const_reference MySTLString::operator[](size_type pos) const
{
    return this->operator[](pos);
}

这是TestMySTLString.cpp(PS:我已删除其他正常功能)

#include "MySTLString.h"
#include <iostream>

using std::cout;
using std::endl;

int main(void)
{
    // test constructor that convert char consequence to MySTLString
    MySTLString s3("qwe");
    return 0;
}

这是调用析构函数的图片

enter image description here

这是调用析构函数时的堆栈信息图片

enter image description here

3 个答案:

答案 0 :(得分:2)

const MySTLString::CharT& MySTLString::front() const
{
    return this->front();
}

将导致无限递归,导致堆栈溢出。如果要重新使用非const版本的实现,则必须使用:

const MySTLString::CharT& MySTLString::front() const
{
    return const_cast<MySTLString*>(this)->front();
}

MySTLString::back()MySTLString::data()进行相同的更改。

答案 1 :(得分:2)

虽然你确实有无限的递归调用(正如其他答案所解释的那样),但它并不是你要问的问题的答案。您遇到的错误是堆损坏错误。

Visual Studio的调试CRT将保护字节分配给堆分配的内存的左侧和右侧,并使用特定的字节模式填充它们。删除内存后,将这些保护字节与字节模式进行比较。如果它们不匹配,您将看到堆损坏对话框,因为您在分配的内存之外写入。

你的d中的错误。 d&amp; tor只是检测到堆损坏的地方,因为它释放了堆分配的内存。

您的代码中存在多个具有相同模式的错误:strlen返回不包括零终止符的字符数。当您strcpy进入已分配的数组时,零终结符将被写入分配的内存之外。

您需要更改以下代码

MySTLString::MySTLString(const char *s)
{
    length_ = strlen(s);
    if (length_ == 0)
    {
        data_ = nullptr;
        return;
    }
    data_ = new char[length_];
    strcpy(data_, s);
}

MySTLString::MySTLString(const char *s)
{
    length_ = strlen(s);
    if (length_ == 0)
    {
        data_ = nullptr;
        return;
    }
    data_ = new char[length_ + 1]; // Allocate enough memory to account for zero terminator
    strcpy(data_, s);
}

确保更新其他事件,并相应地调用strlen


其他随机记录:

  • 在致电nullptr之前,您无需检查delete[]。虽然没有害处,但它也不是必需的。只需在d&#39; tor。
  • 中使用delete[] data_;即可
  • 您的朋友没有错误报告,因为他们大概是使用XCode。 XCode不会报告堆损坏错误,除非专门设置为这样做(在Enabling the Malloc Debugging Features解释了繁琐的过程)。即便如此,它几乎没用。

答案 2 :(得分:1)

查看visual Studio 2015 Build输出。我说

  

data':在所有控制路径上递归,函数将导致运行时堆栈溢出

     

back':在所有控制路径上递归,函数将导致运行时堆栈溢出

     

front':在所有控制路径上递归,函数将导致运行时堆栈溢出

     

operator []':在所有控制路径上递归,函数将导致运行时堆栈溢出

Visual Studio 2015 Build Output