-2

I keep getting an segmentation error(core dumped) when i run this program str.cpp + str.h + main.cpp and I can't for the life of me find out what's causing it. I've tried going through each line of code and replacing variables but still get that error. Here is my code that I was to add to the file given to us by our prof:

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

Output:./str <-- user

Hello

Hello World

123 345 <-- user

Segmentation fault (core dumped)

I want the output to be: ./str <-- user

Hello

Hello World

123 345 <-- user

123 345

If anyone could please help that would be great. I really don't know what else to change/do

  • 1
    A major overhaul is in order. For starters, quit using `new` and `new[]`, `strcpy`, and `strcat`. – Jive Dadson Mar 03 '18 at 04:22
  • 1
    Where does it happen? Use a debugger to find out what’s happening. – Sami Kuhmonen Mar 03 '18 at 04:25
  • Post a [MCVE]. I can tell just by looking that if I cut and paste this into an IDE, it will not compile. – Jive Dadson Mar 03 '18 at 04:25
  • 2
    These horrible homework assignments that are so common on SO make me sad. – Jive Dadson Mar 03 '18 at 04:26
  • Read up on [std::string](http://www.cplusplus.com/reference/string/string/string/) – Ed Heal Mar 03 '18 at 04:27
  • @EdHeal - Sound advice. Tell that to the clueless instructor. – Jive Dadson Mar 03 '18 at 04:29
  • it happens in the = operator function – Steve Guerrera Mar 03 '18 at 04:30
  • You are returning a reference to a local variable. – Jive Dadson Mar 03 '18 at 04:33
  • You are basically forgetting to make room for the null terminator for your raw strings `new char[a._n + 1];` etc... – Galik Mar 03 '18 at 04:39
  • `str s4 = s2 + " " + s3;` -- Where is your copy constructor for `str`? That is what is being invoked on that line, not the `=` operator. – PaulMcKenzie Mar 03 '18 at 04:41
  • 1
    So many daft comments. You can't write a *string* class using `std::string`... – Galik Mar 03 '18 at 04:41
  • I'm getting to the point with homework questions like this where I'm just going to link to a simple, commented, example of a string class or vector class and just have the OP read it, study it, understand it. – PaulMcKenzie Mar 03 '18 at 04:46
  • @SteveGuerrera -- Just post the `str` class declaration, and we will show you what needs to written correctly. Your original post's `main` program will not work correctly, since there are functions you left out that also will fail, such as `str::str(const str&)`, also known as the copy constructor and `str::~str()`. The assignment you were given must have all those functions implemented properly if you want that `main` program to work flawlessly. – PaulMcKenzie Mar 03 '18 at 04:55
  • 3
    @SteveGuerrera -- Also, if your teacher didn't mention [the rule of 3](https://stackoverflow.com/questions/4172722/what-is-the-rule-of-three) to you before giving this assignment, they're either 1) incompetent, or 2) laughing their head off knowing you're going to make futile attempts at fixing the code, or 3) seeing if you are self-taught, can do your own research, and can figure out for yourself this vital information. – PaulMcKenzie Mar 03 '18 at 05:02

3 Answers3

3

The core of what's gone wrong in the question's operator= implementation is that it does no assigning to the destination object. Let's break down what happens:

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

Since everything was stored in the temporary a and not into this, after dest = source; dest comes away unchanged. All of the work done was lost when a went out out of scope at the end of the function and was destroyed.

To fix this up:

The return of the source is a bit strange, but you'll probably never notice the effects once the rest is fixed. Idiomatically, you should return the object that was just assigned. Really good reading on what operator= (and all of the common operator overloads) should look like can be found here: What are the basic rules and idioms for operator overloading? It's simple and easy to get out of the way, so we'll make that our first correction before moving on to the main event.

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

Note the guts of this function look nothing like what sbi suggests. sbi is using the Copy and Swap approach to assignment operators. We'll touch back on this a bit later because it is often an excellent place to start and almost as often an excellent place to stop because it is just so simple that it is almost impossible to get wrong.

Creating a temporary, a, gives you something other than the intended destination, this, to assign to, so let's just get rid of a and use 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;
};

But we don't need to explicitly state this everywhere, so

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

Now the destination object has been assigned a copy of everything in the source, but what if there was stuff in the destination already? Whoops. Just leaked it. Assuming an empty str is nullptr or provided with a valid allocation, the solution to this problem is a quick one:

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

But what if you're doing something silly like a = a and assigning the object to itself?

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

You could simply say, "That's on you, fool." and carry on. There's plenty of that thinking in the C++ standard library so you wouldn't be out of line, but if you want to be forgiving

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

Now we have something that won't embarrass us. Unless s.length() doesn't include the terminating null that allows strcpy to function correctly. Traditionally string length functions do NOT include the terminating null, so we need an extra character to fit in the null terminator. This is the most likely cause of the crash, by the way.

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

Note that you will need this extra byte everywhere you assign a buffer to _buf.

Back to Copy and Swap, sort of.

You need a copy constructor to implement the Copy part of Copy and Swap, but to observe The Rule of Three the object needs to have a copy constructor anyway. Let's take a look at what we need in order to implement one.

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

It is possible to

str a(a);

and construct an object as a copy of its incompletely constructed self. I don't really know why this is legal in C++. Probably because the rules to prevent it would have screwed up something else more important. Doing this is kind of epic-stupid. a = a; can happen because of some confusing indirection where what you actually wrote was a = b;, but a few functions back b was initialized as a reference to a. Or maybe you're working with pointers and screwed up. Anyway, it can happen. str a(a); requires either some deliberation or a typo. Either way it's flat out wrong and should be fixed. I don't recommend trying to protect against this case.

Anyway, we can simplify the copy constructor to

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

by removing the stuff we don't need. And then, through the magic of the Member Initializer List, we can reduce the constructor to

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

And with this beauty in place you can take advantage of Copy and Swap

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

or this, if you have implemented your own swap function for swapping strs.

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

It's very handy to have a swap function if you will be doing sorting of strs later.

Pheeew! Now let's look at 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;
}

This guy's not bad. Needs some extra space for the terminator (c._buf = new char[c._n+1];) but otherwise serviceable. You can probably stop here.

But if you need to also implement operator+= for the assignment, you might as well go with the idiomatic solution. What are the basic rules and idioms for operator overloading? recommends implementing operator+= and basing operator+ around it. That would look something like

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 quick note on Idioms:

In general, one should stick to the idiomatic solutions unless there is a compelling reason not to. They are immediately recognizable to an experienced programmer and require no explanation. This saves time in code reviews and writing documentation and results in fewer surprises when other people work on or with your code. Most importantly they work. They may not work as well as a specialized solution, for example the overhead of the Copy and Swap Idiom can be unnecessarily expensive, but they're a great place to start until it's demonstrated that there is a better solution AND you have a good reason to take advantage of that other solution.

user4581301
  • 33,082
  • 7
  • 33
  • 54
1

You need to change

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

to

 a._buf = new char[a._n + 1];
Ed Heal
  • 59,252
  • 17
  • 87
  • 127
-1

Without the definition of str, it's impossible to say exactly what's needed, but one thing is for sure, you must not return a reference to a local variable. Something like this is required ...

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

EDIT

This is for Jive benefit - as code in comments does not come out too well

str s1("Wibble");  // s1 has memory
str &s2 = s1;      // s2 is a reference to s1

s1 = s2;
Ed Heal
  • 59,252
  • 17
  • 87
  • 127
Jive Dadson
  • 16,680
  • 9
  • 52
  • 65
  • How about the memory leak when assigning a variable to itself? i.e. references – Ed Heal Mar 03 '18 at 04:38
  • @EdHeal - I don't understand. – Jive Dadson Mar 03 '18 at 04:39
  • @SteveGuerrera the problem is that without the definition of `str`, we can only make guesses and they'll likely be wrong. – jdigital Mar 03 '18 at 04:42
  • @SteveGuerrera - " is everyone here as negative as u guys are " Worse. I'm a pussy cat. But I do come down hard on clueless instructors. What kind of design is this? Why re-invent std::string? And re-invent it badly? – Jive Dadson Mar 03 '18 at 04:43
  • I want to post the entire code but that's against the rules here – Steve Guerrera Mar 03 '18 at 04:44
  • I agree its a crap assignment but I'm just trying to get this done the passive aggressive comments don't help in the slightest thank u very much – Steve Guerrera Mar 03 '18 at 04:45
  • 1
    @JiveDadson Students learn by doing. No one is trying to *re-invent* `std::string` here. A good way to learn how classes like `std::string` work is by trying to recreate them. – Galik Mar 03 '18 at 04:56
  • He never returns a reference to a local variable. He returns a reference to the object referred to by his parameter. – Martin Bonner supports Monica Mar 03 '18 at 18:24