0

I've got a simple program that stores sets of 3 variables using two classes, one called Stock which has the 3 variable definitions, along with basic inspector and mutator functions, and a second function called StockManagement that has a pointer to Stock and some other functions that do all sorts of things such as creating a dynamic array of Stock, dumping a files data into the array we just made and so on and so forth.

Everything works fine in terms of creating the stuff (such as the constructor, array, entering data, reading data) but when it comes to the destructor, there is a chance that SOMETIMES the program will seg fault at the delete command, and the times it doesn't, the data is still accessable for some reason or another.

Here's what I believe to be the important parts of the code

Header file

class Stock {
private:
    char tag[5];
    int cost;
    long volume;

public:
    void setTag(char);
    void setCost(int);
    void setVolume(long);

    char* getTag();
    int getCost();
    long getVolume();

}

class StockManagement {
private:
    Stock* data;
    int size;

public:
    StockManagement();
    ~StockManagement();

   // other functions
}

Cpp file (the proper name escapes me)

...

void Stock::setTag(char tag) {
    this->tag = tag;
}

void Stock::setCost(int cost) {
    this->cost = cost;
}

void Stock::setVolume(long volume) {
    this->volume = volume;
}

char* Stock::getTag() {
    return this->tag;
}

int Stock::getCost() {
    return this->cost;
}

long Stock::getVolume() {
    return this->volume;
}

// --

StockManagement::StockManagement() {
    data = NULL;
    size = 0;
}

StockManagement::~StockManagement() {
    if (data != NULL) {
        std::cout << data[0].getTag() << std::endl; // Debug line
        delete [] data;
        std::cout << data[0].getTag() << std::endl; // Debug line

        data = NULL;
        size = 0;
    }
}

void StockManagement::allocateMemory() {
    data = new Stock[size];
}

...

So at the point in time when calling the destructor, there is always data in there. Theres a function always called that gets the amount of lines from a file (and saves it as size) which is used to allocate the memory, in which data is then entered into the array.

Then comes time to use the destructor. The first cout line will always output, as expected. Then theres a chance that either two things happen from then on. We seg fault at the delete line, or we go over it, calling the second cout and somehow printing the same data we printed the first time.

So obviously the data wasn't deleted. But why? And why does this only happen sometimes and other times it just straight up seg faults?

SteppingHat
  • 1,199
  • 4
  • 19
  • 50
  • Accessing the object after destruction is UB. – Karoly Horvath Oct 03 '15 at 16:37
  • 4
    Deleting allocated memory doesn't remove the physical chip from the computer. The memory is still there, its just given to someone else. – Galik Oct 03 '15 at 16:47
  • 1
    @Galik I called `delete` one too many times and had to download some more RAM. – bku_drytt Oct 04 '15 at 13:49
  • @Galik so `delete` just gets rid of the pointer but not the data associated with it, until it's overwritten by something else? I could see that being a potential exploit in so many different ways... – SteppingHat Oct 04 '15 at 16:02
  • 1
    @SteppingHat I over-simplified a little, `delete` doesn't (by itself) get rid of anything, it simply marks that part of memory for re-use. The `C++` *runtime system* can then do what it likes with it, either giving it to another caller (in the same process) or handing it back to the OS liberating it for use in other processes. – Galik Oct 04 '15 at 16:26
  • 1
    `StockManagement` appears to violate the [Rule of 3](https://stackoverflow.com/questions/4172722/what-is-the-rule-of-three), which I assume is the crux of your bug – Mooing Duck Feb 06 '23 at 19:17

1 Answers1

-1

You have this for your constructor and destructor.

StockManagement::StockManagement() {
    data = NULL;
    size = 0;
}

StockManagement::~StockManagement() {
    if (data != NULL) {
        std::cout << data[0].getTag() << endl; // Debug line
        delete [] data;
        std::cout << data[0].getTag() << endl; // Debug line

        data = NULL;
        size = 0;
    }
}

As there is nothing wrong with your constructor but as a personal preference I prefer this:

StockManagement::StockManager() :
  data( nullptr ),
  size( 0 )
{}

For your destructor I see a problem within the code

StockManagement::~StockManagement() {
    if ( data != NULL ) {
        std::cout << data[0].getTag() << endl; // Debug Line
        delete[] data;
        std::cout << data[0].getTag() << endl; // Debug Line

        data = NULL;
        size = 0;
    }
 }

I see a few errors and can optimize for easier reading. Your first error is that you are using std::cout so I am assuming you do not have using namespace std; defined. This means that you will also need to use the scope resolution operator as well for endl; you need to have it declared as ... std::endl;

Your second error is after you call delete [] data; you then on the next line are trying to access the element at index 0 of data and calling a getTag() method. This is undefined behavior and can cause crashes, unhandled exceptions etc.

As for readability, your naming conventions in you class members could use a little bit of work. Also one thing to think of is for any pointer member in your class you can use either std::unique_ptr<ClassType> or std::shared_ptr<ClassType> depending on the need. If this class has sole ownership of the object and you do not want any outside object to modify it then use unique but if you need other objects to work with this data member, modify then the class saves the modified data you can use shared. The beauty of unique & shared is when the object goes out of scope you do not have to worry about calling delete or delete[] on that object and you are preventing possible memory leaks. I would also replace NULL for any raw pointer with nullptr if your compiler supports it, but most if not all modern compilers should already support this by now. Instead of having this in your class declaration if you plan on using the new and delete | delete [] operators.

private:
    Stock* data;

I would suggest this

private:
    std::shared_ptr<Stock> data;
    // Or
    std::unique_ptr<Stock> data;

I hope this helps you out.

Edit:

I also noticed that in your StockManagement class you define a pointer to a Stock object which is a class object, you then initialize it to be NULL or a nullptr in your constructor, but within your destructor you are calling delete [] on data. Normally delete [] is used on an array of pointers which I do not see defined in your code. You do have a member function to allocate memory using new with the bracket operator but you have only defined your member variable as a pointer to an object, not a pointer to a pointer of objects.

Francis Cugler
  • 7,788
  • 2
  • 28
  • 59
  • In terms of the endl, I actually am using namespace std, but the file is well over 500 lines of code and I didn't want to slap it all here, in doing so I left out the namespace declaration, tried to compensate by adding the `std::` here in the SO question only and simply forgot to do endl. On the subject of accessing the element after deletion, this was only part of debugging to see if it ACTUALLY deleted it, which it doesn't. The error either happens at the delete line or not at all and the program continues as there is data still there. – SteppingHat Oct 04 '15 at 02:49
  • I don't see how this is a solution however. In the edit you mentioned `delete []` is used for an array of pointers, in which case this is as as soon as an instance of the class is created the the function `allocateMemory()` is called, which using the pointer as a reference parameter creates an array of pointers – SteppingHat Oct 04 '15 at 02:53
  • That does clarify a few things; it was hard to determine with minimal code to look at, so I had to make a lot of assumptions. – Francis Cugler Oct 04 '15 at 09:31
  • I would still try to get into the practice of using smart & unique pointers when working with the heap. Raw pointers are okay if they are stack only or the are completely private and 100% managed by a single class object. – Francis Cugler Oct 04 '15 at 09:32
  • 1
    You are not showing where or how your StockManagement is either accepting or setting the pointer to your Stock Object and you are not showing how either your Stock or StockManagement objects are being constructed with data. – Francis Cugler Oct 04 '15 at 09:49
  • I totally get what you mean. I'm afraid that this is part of a uni assignment and this is the scope of code we are allowed to use. It's driving me crazy in a way not being able to use much more that I know would make things better/easier. To help clarify what I'm doing, I've edited the question to contain the inspector and mutator functions, which I probably should have included from the beginning, but in a file of 500+ lines it didn't occur to me to include it. Sorry for limiting your scope. – SteppingHat Oct 04 '15 at 13:50
  • But basically in StockManagement, there are some other functions that use those mutator/inspector functions to read and write data to the dynamic array. Nothing else actually touches the data, just tells it what to do, which is why they are in a private area of its own class. – SteppingHat Oct 04 '15 at 13:51