1
votes

I have an assignment where I need to use an overloaded destructor to delete dynamically allocated pointers. However, when it runs, some of my pointers are deleted until a segmentation fault with one of my objects' pointers the one pointing to " and the second" made with a parameterized constructor. I have tried to go through and make sure the delete operator had brackets (because my new operator did) I made sure the object still existed by printing out its information and address. I have tried to rewrite my allocation function, and I have tried to go over my destructor to see where it messes up. If it helps, I have included my destructor, my allocation function, my deallocation function, and my parameterized constructor.

'''
//Destructor
MyString::~MyString()
{
  buffer_deallocate();
};

void MyString::buffer_deallocate() {
  cout << m_buffer << endl;
  delete[](m_buffer);
  m_buffer = NULL;
  m_size = 0;

}

void MyString::buffer_allocate(size_t size) {
  try {

    m_buffer = new char[size];
    m_size = size;
  }
  catch(bad_alloc&)
    {
      cout << "Errror: Unable to allocate memory" << endl;
      buffer_deallocate();
    }

}


//Parameterized Constructor
MyString::MyString(const char * str)
  :m_size(0)
{
  const char * strPtr = str;
  while(*strPtr)
    {
      strPtr++;
      m_size++;
    }

    buffer_allocate(m_size);

    for(int i = 0; i < m_size; i++)
      {
        m_buffer[i] = str[i];
      }

};
'''

Every time however I get the output after "and the second" to be Segmentation fault (core dumped)

Edit: I have tried the majority of what was recommended. At least what I understood, the problem still persists, and I realize now I was kind of sparse on my code. (Please forgive me, I'm still learning.) Here is the new code along with the rest of the function file for reference:

'''
#include<iostream>
#include<string.h>
using namespace std;

#include"MyString.h"


//Default Constructor
MyString::MyString()
:m_size(0), m_buffer(NULL)
{
        buffer_allocate(0);
};



//Parameterized Constructor
MyString::MyString(const char * str)
  :m_size(strlen(str)+1), m_buffer(NULL)
{
        buffer_allocate(m_size);
        strncpy(m_buffer, str, m_size);

};



//Copy Constructor
MyString::MyString(const MyString & other)
  :m_size(0), m_buffer(NULL)
{
  const char * otherPtr = other.c_str();
  buffer_allocate(other.size());

  for(int i = 0; i < size(); i++)
    {
      m_buffer[i] = otherPtr[i];
    }
        m_buffer[m_size] = '\0';
};


//Destructor
MyString::~MyString()
{
  buffer_deallocate();
};



size_t MyString::size() const
{
  return m_size;
}



size_t MyString::length() const{
  return m_size-1;
}



const char * MyString::c_str() const{
  return m_buffer;
}


bool MyString::operator==(const MyString & other) const {
  char * m_bufferPointer = m_buffer;
  while(*m_bufferPointer++)
    {
      const char * str_ptr = other.c_str();
      if(*m_buffer != *str_ptr++)
        {
          return 0;
        }
    }
  return 1;
}


MyString & MyString::operator=(const MyString & rhs) {
  buffer_deallocate();

  buffer_allocate(rhs.size());
  const char * c_strPtr = rhs.c_str();
  int i;
  for(i = 0; i < rhs.size(); i++)
    {
      this->m_buffer[i] = c_strPtr[i];
    }
  return *this;
}




MyString MyString::operator+ (const MyString & other_myStr) const {

  char * temp_pointer;
  temp_pointer;
  size_t temp_size = m_size + other_myStr.size();
  //New Combined Buffer for Concatanation
  try {
    temp_pointer = new char[temp_size];
    temp_pointer = strcat(this->m_buffer, other_myStr.c_str());

  }
  catch(bad_alloc&)
    {
      cout << "Error: Unable to Allocate Memory";
      return NULL;
    }
  return MyString(temp_pointer);
}



char & MyString:: operator[](size_t index) {
  return m_buffer[index];
}


const char & MyString::operator[] (size_t index) const {
  return m_buffer[index];
}


ostream & operator << (ostream& os, const MyString & myStr) {
  os << myStr.m_buffer;
  return os;

}

void MyString::buffer_deallocate() {



  cout << "Trying to delete : " <<m_buffer << endl;
        if(m_buffer){
                delete[](m_buffer);
        }
        cout << " Success" <<endl;
  m_buffer = NULL;
  m_size = 0;

}

void MyString::buffer_allocate(size_t size) {
        try {

    m_buffer = new char[size];
    m_size = size;
  }
  catch(bad_alloc&)
    {
      cout << "Errror: Unable to allocate memory" << endl;
        m_size = 0;
    }

}

'''

2
buffer_allocate isn't really a good place to catch that exception and then return void. The program will carry on happily thinking that storage was allocated because no one told it to do anything else. The error message printed to the screen is only useful to the user AFTER the program has tried to access a null pointer and failed. - user4581301
Are you following the rule of 3/5/0? - François Andrieux
The while(*strPtr) loop is effectively strlen. No need to reinvent the wheel here. - user4581301
Another thing to watch out for in buffer_allocate: What if m_buffer is already pointing at an allocation? - user4581301
Is the copy constructor implemented? Is the assignment operator implemented? If not implemented, are they explicitly deleted? - Eljay

2 Answers

0
votes

in MyString::buffer_deallocate

cout << m_buffer << endl;

requires that m_buffer be null-terminated. Unfortunately, MyString::MyString(const char * str) does not make this guarantee.

You could

for(int i = 0; i < m_size; i++)
{
    cout << m_buffer[i] << endl;
}

to print the string out character by character instead, but it is probably more useful to waste a byte, null terminate, and take advantage of the Standard library

MyString::MyString(const char * str)
  :m_size(0)
{
  const char * strPtr = str;
  while(*strPtr)
    {
      strPtr++;
      m_size++;
    }

    buffer_allocate(m_size); 

    for(int i = 0; i < m_size; i++)
      {
        m_buffer[i] = str[i];
      }
    m_buffer[m_size] = '\0'; // add the null
}

and then

void MyString::buffer_allocate(size_t size) {
  try {

    m_buffer = new char[size+1]; // +1 for the null terminator
    m_size = size;
  }
  catch(bad_alloc&) // this is a bad thing to do here. More on that later.
    {
      cout << "Errror: Unable to allocate memory" << endl;
      buffer_deallocate();
    }

}

But we can simplify this with a few library function calls.

MyString::MyString(const char * str)
  :m_size(strlen(str))
{
    buffer_allocate(m_size);
    strcpy(m_buffer, str);
}

Addendum:

Your class may be violating the Rule of Three. If MyString does not have a copy constructor and an assignment operator to go along with the destructor any copies, deliberate or accidental, will turn a MyString into a time bomb. The destructor of one of the copies will be run before the others leaving the others without a valid m_buffer.

MyString::buffer_allocate cannot safely return void unless it allows the exception to propagate. Catching the bad_alloc leaves the object without a valid allocation at m_buffer and the rest of the program will not know this. Either every other access in the program must test for a valid buffer or engage in undefined behaviour trying to access the invalid memory. It's likely better to let the exception fall through and be caught by another part of the program that is better suited to making a decision on what to do.

MyString::buffer_allocate will leak an existing allocation if called on a MyString that already has a valid allocation at m_buffer. I recommend a

if (m_buffer) 
{
    delete[] m_buffer;
}

and initializing m_buffer to null in MyString's constructor

MyString(const char* str)
    : m_size(std::strlen(str)), m_buffer(nullptr) 
{
    buffer_allocate(m_size);
    std::strncpy(m_buffer, str, m_size);
}
0
votes

So I had to remake this to some working code. The problem might be that a string length must be extended with 1 to add the nul-termination. So that makes:

// prevent some MSVS warnings->errors
#define _CRT_SECURE_NO_WARNINGS

#include <iostream>
#include <string>
#include <cstring>


//Destructor
class MyString {
private:
    size_t m_size;
    char* m_buffer;

    void buffer_allocate(size_t size) {
        try {
            m_buffer = new char[size];
            m_size = size;
        }
        catch (std::bad_alloc&)
        {
            std::cout << "Errror: Unable to allocate memory\n";
            // allocation failed, nothing changed, don't delete/
            m_size = 0;
        }

    }

    void buffer_deallocate() {
        // printing in the buffer deallocation??
        delete[] m_buffer;
        m_buffer = nullptr;
        m_size = 0;
    }

public:
    MyString(const char* str)
        : m_size(std::strlen(str)+1) // add null termination
        , m_buffer(nullptr)
    {
        buffer_allocate(m_size);
        std::strncpy(m_buffer, str, m_size);
    }

    ~MyString() {
        buffer_deallocate();
    }

    void Print() const {
        std::cout << m_buffer << '\n';
    }
};

int main() {
    std::string input{ "Hello World!" };
    MyString myString(input.c_str());
    myString.Print();
}