0

Below is a snippet of my code for a stack data structure that I am trying to implement.

For some reason when I delete currentArray, newArray is deleted too, because the code below is giving me a run-time bug where the contents of newArray and currentArray are garbage values.

I'm not sure why this is happening.

Would greatly appreciate any insight as to why I am getting this bug, and if my push() implementation below is on point from a fundamental perspective.

// Destructor
~Stack() 
{
    if ((size > 0) && (currentArray) != NULL && (newArray != NULL))
    {
        delete[] currentArray;
        delete[] newArray;
    }
}

inline void push(T value)
{
    if (isEmpty())
    {
        // Stack size is increased by 1
        size++;

        currentArray = new T[size];
        currentArray[size - 1] = value;
    }
    else
    {
        // Stack size is increased by 1
        size++;

        // Create the new array
        newArray = new T[size];

        // Copy the contents of the old array into the new array
        copy(currentArray, currentArray + size, newArray);

        // Push the new value on to the new array
        newArray[size - 1] = value;

        // Copy the new array into the current
        currentArray = new T[size];
        currentArray = newArray;
    }
}
Bond
  • 16,071
  • 6
  • 30
  • 53
Andre Rogers
  • 47
  • 3
  • 10
  • First, please post a complete but small program that demonstrates the error. Second, your destructor for `Stack` looks strange -- why do you need all of those conditions to `delete[]` the memory? Just `delete[]` it without all of those conditions. – PaulMcKenzie Jul 25 '15 at 01:06
  • 1
    Also, where is the `delete[ ]` in your `push` function to get rid of the old memory? If `currentArray` was already allocated, you've created a memory leak. That's why we need to see everything, not a snippet. I bet that your troubles with this class start much earlier and in code that you are not showing us. – PaulMcKenzie Jul 25 '15 at 01:10
  • Optimization note. When increasing the size of the stack, don't just increase the size by one because the time spent copying the stack at every addition will kill your performance. Increase the size by some reasonable number. Perhaps even double the size. – user4581301 Jul 25 '15 at 01:46

4 Answers4

2

In your push function, in the else block you do

currentArray = newArray;

So both your currentArray and newArray are pointing to the same thing! (which you are doing yourself).

So, when you do delete [] currentArray you need not delete newArray since that memory has already been deleted. And that is why your program gives a fault that you are trying to delete memory which has already been deleted!!

Moreover in your push function you do ::

currentArray = new T[size];
currentArray = newArray;

I believe in your class, currentArray is a pointer and new also returns the pointer to the newly allocated memory. So, if you want your currentArray to point to the newArray, you simply need to do currentArray = newArray without allocating memory to the currentArray.

Further, you also do ::

 newArray = new T[size];
 copy(currentArray, currentArray + size, newArray);

inside your if block, I believe you are copying the data currentArray to a newArray so, after you have copied the data from currentArray to newArray you need to delete previously allocated memory to the currentArray, otherwise it is a memory-leak (A failure in a program to release discarded memory, causing impaired performance or failure). It is always to good to delete memory which is of no use.

So, I would recommend you add delete [] currentArray statement after this.

So, your final code shall look something like this ::

// Destructor
~Stack() 
{
    /*
     *if ((size > 0) && (currentArray) != NULL) --> 
     *deleting a NULL pointer is completely defined!
     *So, in your constructor if you are initializing 
     *currentArray to NULL you need not have any if condition.
    */
    delete[] currentArray;
}

inline void push(T value)
{
    if (isEmpty())
    {
        size++;

        currentArray = new T[size];
        currentArray[size - 1] = value;
    }
    else
    {
        size++;

        newArray = new T[size];

        copy(currentArray, currentArray + size, newArray);

        delete [] currentArray; //EDIT

        newArray[size - 1] = value;

        //currentArray = new T[size]; --> Deleted Line
        currentArray = newArray;
    }
}

NOTE :: Just in case your compiler is in compliance with C++11, you shall consider using nullptr over NULL, here is why : What are the advantages of using nullptr?

Community
  • 1
  • 1
user007
  • 2,156
  • 2
  • 20
  • 35
  • If the destructor requires conditions to do delete, then there is a fundamental flaw in the class. The destructor should be able to call `delete []` on the memory without any condition. In other words `~Stack() { delete [ ] currentArray; }` If that causes problems, then the class is faulty. – PaulMcKenzie Jul 25 '15 at 01:32
  • Yea.. I just copied the code from there. If `currentArray` was initialized to `NULL` probably removing the `if` conditions won't make a difference! – user007 Jul 25 '15 at 01:37
  • 2
    Comical. @Bond edited the OP's question. User007 answered. – user4581301 Jul 25 '15 at 01:38
  • Thank you. This worked. I was deleting new array before but I was getting an error, that is why I moved it to the destructor. Anyway, it seems to be working now. Thank you. What would you suggest I do for pop() in terms of preventing a memory leak. – Andre Rogers Jul 28 '15 at 20:40
  • @Andre you will have to show the `pop()` code for that. *What is a memory leak ? :: A failure in a program to release discarded memory, causing impaired performance or failure.* (Just in case you weren't clear what a memory leak is) – user007 Jul 28 '15 at 22:55
2

Firstly, you don't need the checks in the destructor.

~Stack() {
    delete [] currentArray;
    delete [] newArray;
}

The push has several problems:

  1. You pass value by value, which might be expensive. You should pass by reference.
  2. You use size in the copy() after incrementing it, which means you're copying raw memory into the last slot of newArray. This might be benign (T = int + a bit of luck) or disastrous (T = std::string).
  3. newArray is only needed inside push(). It should be a local variable, not a member variable.
  4. You grow by 1 each time, which results in O(n2) time to populate. You should grow geometrically, which requires an additional capacity member variable.
  5. You invoke new T[size] twice. One of them leaks.

Here's a reworked version:

class Stack {
public:
    …
    inline void push(const T& value) {
        if (size_ == capacity_) {
            capacity_ = capacity_ ? capacity_ * 2 : 1;
            T* dest = new T[capacity_];
            copy(data_, data_ + size_, dest);
            delete [] data_;
            data_ = dest;
        }
        data_[size_++] = value;
    }
    …
private:
    T* data_ = nullptr;
    size_t size_ = 0, capacity_ = 0;
};

This is not by any means a solid, industrial-strength piece of code. It doesn't address exception safety, copy-avoidance (requiring, among other things, an additional overload: push(T&& value)), and a range of other subtleties. In fact, I haven't even checked that it compiles! It might do for a toy project, but in the real world you should simply use std::stack.

Marcelo Cantos
  • 181,030
  • 38
  • 327
  • 365
  • "You use size in the copy() after incrementing it, which means you're copying raw memory into the last slot of newArray. This might be benign (T = int + a bit of luck) or disastrous (T = std::string)." Can you explain this, because I do intend on using strings. This implementation is a pre-cursor to implementing a lexical analysis. Can you also explain this point "You grow by 1 each time, which results in O(n2) time to populate. You should grow geometrically, which requires an additional capacity member variable." Thank you. – Andre Rogers Jul 28 '15 at 20:43
  • would it be better to use char* instead of string? – Andre Rogers Jul 30 '15 at 01:54
  • @Andre It would be better to not try to copy unallocated space. A vector of `char *` would cause far more headaches than it's worth, anyway. – Marcelo Cantos Jul 31 '15 at 10:12
  • @Andre To explain the first point, if you have 4 elements, and try to push a 5th element, your code first increments size, and then calls `copy(currentArray, currentArray + size, …)` which is effectively `copy(currentArray, currentArray + 5)`, but your source array only has four elements in it at this. So you will be attempting to copy a non-existent 5th element, before even thinking about inserting the actual 5th element. – Marcelo Cantos Jul 31 '15 at 10:15
  • @Andre Wrt geometric growth: Imagine inserting 10 elements. Using your approach, the first insertion will be fine. The second insertion will require copying the first element. The third insertion will copy the first two elements, etc. Inserting all 10 elements will cost 1+2+3+4+5+6+7+8+9 = 45 copies. In general, n insertions costs n(n - 1)/2 copies, which is O(n²). Doubling the array size for each insertion that doesn't fit is much more efficient. A similar analysis shows that n insertions only require O(n) copies. – Marcelo Cantos Jul 31 '15 at 10:26
  • I know I could use vector or stack from STL to create it, am trying to implement it decently optimized just for fun, to brush up on my C++. Is char* better handled than string by the C++ compiler? In your above implementation of `push()` isn't there extra space allocated in `data[]`, is that not a bad thing to do? I was trying to avoid having extra space being allocated to it, that's why I was allocating it 1 block at a time, but I understand why you suggested geometric progression, makes more sense. – Andre Rogers Aug 01 '15 at 04:57
  • Sure, `std::string` is more work for the compiler, but `char *` puts all the work on you, and you're far more likely to screw up than the compiler. – Marcelo Cantos Aug 01 '15 at 07:13
0

Maybe I'm missing something, but if the if statement proves true, it deletes both anyhow.

Also, from a basic perspective, why wouldn't you have:

if ((size > 0) && (currentArray != NULL) && (newArray != NULL))

It's not a huge deal, but mismatched parentheticals are the enemy.

Jesse Williams
  • 653
  • 7
  • 21
0

@user007 this is my pop() function.

// Remove an element from the stack
inline const T& pop()
{
    if (size > 0)
    {
        T value;

        size--;

        value = data[size];

        T* dest = new T[size];

        // Copy the contents of the old array into the new array
        copy(data, data + (size), dest);

        delete[] data;

        data = dest;

        return value;
    }

    return NULL;
}
Andre Rogers
  • 47
  • 3
  • 10