我不断收到分段错误,但不知道是什么原因造成的

I keep getting a segmentation error and have no idea what's causing it?

本文关键字:是什么 不知道 错误 分段      更新时间:2023-10-16

当我运行这个程序str.cpp + str.h + main时,我不断收到分段错误(核心转储.cpp我一生都无法找出导致它的原因。我尝试遍历每一行代码并替换变量,但仍然收到该错误。这是我的代码,我要添加到我们的教授提供给我们的文件中:

//str.cpp
const str& str::operator=(const str& s)
{
str a;          
a._n = s.length();
a._buf = new char[a._n];
strcpy(a._buf, s._buf); 
return s;

};
str operator+(const str& a, const str& b)
{
str c ; 
c._n = a._n + b._n;
c._buf = new char[c._n];
strcpy(c._buf,a._buf);
strcat(c._buf ,b._buf);  
//strcat(strcat(c._buf,""),b._buf);
return c;
}
//main.cpp
#include "str.h"
int main() {
str s1;
cout << s1 << endl;
str s2("Hello");
cout << s2 << endl;
str s3("World");
str s4 = s2 + " " + s3;
cout << s4 << endl;
str s5, s6;
cin >> s5 >> s6;
cout << s5 << ' ' << s6;
return 0;
}

输出:./str <-- 用户

你好

世界您好

123 345 <-- 用户

分段故障(核心转储)

我希望输出为: ./str <-- 用户

你好

世界您好

123 345 <-- 用户

123 345

如果有人能帮忙,那就太好了。我真的不知道还能改变/做什么

问题operator=实现中出错的核心是它不分配给目标对象。让我们分解一下会发生什么:

const str& str::operator=(const str& s)
{
str a;                   // make a temporary
a._n = s.length();       // assign source's length to temporary's length
a._buf = new char[a._n]; // assign a buffer to the temporary
strcpy(a._buf, s._buf);  // assign source's data to temporary
return s;                // return source
}; // temporary goes out of scope and is destroyed

由于所有东西都存储在临时a而不是this中,因此dest = source;dest之后保持不变。当a在功能结束时超出范围并被销毁时,所有完成的工作都丢失了。

要解决此问题:

源的返回有点奇怪,但是一旦其余部分修复,您可能永远不会注意到效果。按照习惯,您应该返回刚刚分配的对象。关于operator=(以及所有常见的运算符重载)应该是什么样子的真正很好的阅读可以在这里找到:运算符重载的基本规则和习语是什么?这既简单又容易摆脱困境,因此我们将在进入主要事件之前进行第一次更正。

str& str::operator=(const str& s)
{
str a;
a._n = s.length();
a._buf = new char[a._n];
strcpy(a._buf, s._buf);
return *this;
};

请注意,此函数的内涵看起来与 sbi 建议的完全不同。 sbi 对赋值运算符使用复制和交换方法。我们稍后会谈到这一点,因为它通常是一个很好的起点,几乎同样经常是一个停下来的好地方,因为它是如此简单,几乎不可能出错。

创建一个临时的a,会为您提供除预期目的地以外的其他内容,this,以分配给您,因此让我们摆脱a并使用this

const str& str::operator=(const str& s)
{
this->_n = s.length();
this->_buf = new char[this->_n]; 
strcpy(this->_buf, s._buf);
return *this;
};

但是我们不需要在任何地方都明确声明this,所以

const str& str::operator=(const str& s)
{
_n = s.length();
_buf = new char[_n]; 
strcpy(_buf, s._buf);
return *this;
};

现在,目标对象已被分配了源中所有内容的副本,但是如果目标中已经有内容怎么办?哎 呦。只是泄露了它。假设nullptrstr或提供了有效的分配,则此问题的解决方案是快速的:

const str& str::operator=(const str& s)
{
delete[] _buf;
_n = s.length();
_buf = new char[_n]; 
strcpy(_buf, s._buf);
return *this;
};

但是,如果您正在做一些像a = a这样的愚蠢事情并将对象分配给自己怎么办?

const str& str::operator=(const str& s)
{
delete[] _buf; // same object so this deleted s._buf
_n = s.length();
_buf = new char[_n]; 
strcpy(_buf, s._buf); // and s._buf is gone, replaced by the new empty _buf
// bad stuff will happen here
return *this;
};

你可以简单地说,"那是你的事,傻瓜。C++标准库中有很多这样的想法,所以你不会出格,但如果你想宽容

const str& str::operator=(const str& s)
{
if (this != &s)
{
delete[] _buf;
_n = s.length();
_buf = new char[_n]; 
strcpy(_buf, s._buf);
}
return *this;
};

现在我们有一些不会让我们尴尬的东西。除非s.length()不包含允许strcpy正常运行的终止 null。传统上,字符串长度函数不包括终止 null,因此我们需要一个额外的字符来适应 null 终止符。顺便说一下,这是崩溃的最可能原因。

const str& str::operator=(const str& s)
{
if (this != &s)
{
delete[] _buf;
_n = s.length();
_buf = new char[_n + 1]; // +1 for null terminator
strcpy(_buf, s._buf);
}
return *this;
};

请注意,在为_buf分配缓冲区的所有位置都需要这个额外的字节。

回到复制和交换,有点。

你需要一个复制构造函数来实现复制和交换的复制部分,但要遵守三法则,对象无论如何都需要有一个复制构造函数。让我们来看看我们需要什么才能实现一个。

str::str(const str& s)
{
if (this != &s) // don't need because you have to be really stupid to copy yourself
{
delete[] _buf; // don't need because this is a new object. It can't have an 
// existing allocation
_n = s.length();
_buf = new char[_n + 1];
strcpy(_buf, s._buf);
}
};

有可能

str a(a);

并构造一个对象作为其不完全构造的自我的副本。我真的不知道为什么这在C++是合法的。可能是因为防止它的规则会搞砸其他更重要的事情。这样做有点史诗般的愚蠢。a = a;可能发生,因为一些令人困惑的间接寻址,您实际编写的内容是a = b;,但是一些函数b被初始化为对a的引用。或者,也许您正在使用指针并搞砸了。无论如何,它可能发生。str a(a);需要一些深思熟虑或错别字。无论哪种方式,它都是错误的,应该被修复。我不建议尝试防止这种情况。

无论如何,我们可以将复制构造函数简化为

str::str(const str& s)
{
_n = s.length();
_buf = new char[_n + 1];
strcpy(_buf, s._buf);
};

通过删除我们不需要的东西。然后,通过成员初始值设定项列表的魔力,我们可以将构造函数简化为

str::str(const str& s): _n(s._n), _buf(new char[_n + 1])
{
strcpy(_buf, s._buf);
};

有了这种美感,您就可以利用复制和交换

str& str::operator=(str s) // copy constructor does all the copying
{
std::swap(_n, s._n); // exchange guts with s
std::swap(_buf, s._buf);
return *this;
}; // destruction of s deletes this's old _buf

或者这个,如果你已经实现了自己的swap函数来交换strs。

str& str::operator=(str s)
{
swap(*this, s);
return *this;
}; 

如果您稍后要对str进行排序,那么拥有一个swap函数非常方便。

噗!现在让我们看看operator+

str operator+(const str& a, const str& b)
{
str c ; 
c._n = a._n + b._n;
c._buf = new char[c._n]; // need a +1 here for the null terminator
strcpy(c._buf,a._buf);
strcat(c._buf ,b._buf);  
//strcat(strcat(c._buf,""),b._buf);
return c;
}

这家伙还不错。需要一些额外的空间来放置终结器(c._buf = new char[c._n+1];),但其他方面可以维修。你可能可以停在这里。

但是,如果您还需要为作业实现operator+=,则不妨使用惯用的解决方案。运算符重载的基本规则和习语是什么?建议实施operator+=并围绕它进行operator+。那看起来像

str& str::operator+=(const str& b)
{
_n += b._n;
char * temp = new char[_n+1]; // can't use _buf yet. Need free it's memory and
// to copy from it
strcpy(temp, _buf);
strcat(temp, b._buf);  
delete[] _buf; // now we can release and reuse _buf
_buf = temp;
return *this;
}
str operator+(str a, const str& b)
{
a+=b;
return a;
}

关于成语的快速说明:

一般来说,除非有令人信服的理由不这样做,否则应该坚持惯用的解决方案。经验丰富的程序员可以立即识别它们,不需要解释。这样可以节省代码审查和编写文档的时间,并在其他人处理或使用您的代码时减少意外。最重要的是他们工作。它们可能不如专门的解决方案工作,例如,复制和交换习惯用法的开销可能不必要地昂贵,但它们是一个很好的起点,直到证明有更好的解决方案并且你有充分的理由利用其他解决方案。

你需要改变

a._buf = new char[a._n];

a._buf = new char[a._n + 1];

如果没有str的定义,就不可能确切地说出需要什么,但有一件事是肯定的,你不能返回对局部变量的引用。需要这样的东西...

const str& str::operator=(const str& s)
{      
this->_n = s.length();
this->buf = new char[s._n]; // Plus one?
strcpy(this->buf, s._buf); 
return *this;
};

编辑

这是为了 Jive 的好处 - 因为注释中的代码效果不太好

str s1("Wibble");  // s1 has memory
str &s2 = s1;      // s2 is a reference to s1
s1 = s2;
相关文章: