简体   繁体   中英

Heap Corruption, Possible Memory Leaks, C++

I have a homework assignment that I've nearly completed.

As inefficient as it is be I was just wanting to know how I can prevent a crash when my program ends.

quack::quack(int capacity) : backPtr( NULL ), frontPtr( NULL )
{
    items    = new item[capacity];
    backPtr  = new item;
    frontPtr = new item;
    midPtr   = new item;
    current  = new item;

    maxSize = capacity;
    back    = maxSize-1;
    count   =  0;
    top     = -1;
}

quack::~quack(void)
{
    delete frontPtr;
    delete backPtr;
    delete current;
    delete midPtr;
    delete [] items; //Heap Corruption Debug Error at the end of program.

    items    = NULL;
    maxSize  =  0;
    back     =  0;
}

bool quack::pushFront(const int n)
{       
    int i = 0;

    if ( count == maxSize ) // Then we cant add to it n e more. 
    {   
        throw runtime_error( "Stack is Full" );// Full Stack
        return false;
    }
        backPtr->n = items[back-1].n;
        while ( i < count ) // Loop less than however many we counted.
        {
            if ( i == top+1 )
            {
                current->n = items[top+1].n;   
                items[top+1].n = backPtr->n;
            }

            midPtr->n  = items[++i].n;
            items[i].n = current->n;

            if ( i != back-1 )
            {
                current->n = items[++i].n;
                items[i].n = midPtr->n;
            }
        }
        ++count;
        items[top+1].n = n;
        return true;    
}

bool quack::pushBack(const int n)
{   
    items[count].n = n;
    count++;
    return true;
}

bool quack::popFront(int& n)
{
    n = items[top+1].n;
    for ( int i = 0; i < count; i++ )
    {
        items[i] = items[i+1];
    }
    count--;   // Remove top element.
    return true;
}

bool quack::popBack(int& n)
{

    n = items[--count].n;
    return true;
}

void quack::rotate(int r)
{
    int i = 0;

    while ( r > 0 ) // rotate postively.
    {
        frontPtr->n = items[top+1].n;
        for ( int i = 0; i < back; i++ )
        {
            items[i] = items[i+1];                  
        }
        items[back-1].n = frontPtr->n;
        r--;
    }

    while ( r < 0 )  // rotate negatively.
    {
        if ( i == top+1 )
        {
            backPtr->n = items[back-1].n;
            current->n = items[top+1].n;  
            items[top+1].n = backPtr->n;
        }
            midPtr->n  = items[++i].n;
            items[i].n = current->n;

            if ( i == back-1 )
            {
                items[back-1].n = current->n;
                i = 0;
                r++; continue;
            }
        else
        {
            current->n = items[++i].n;
            items[i].n = midPtr->n;
            if ( i == back-1 )
            {
                i = 0;
                r++; continue;
            }
        }
    }
}

void quack::reverse(void)
{
    int j = 0; // Variable declaration/initialization.

    frontPtr->n = items[top+1].n;
    backPtr->n  = items[back-1].n;

    for ( int i = 0; i < count / 2; i++ )
    {
        items[j].n = items[i].n;
        items[i].n = items[ count - i-1 ].n;
        items[ count - i-1 ].n = items->n;
    }

    items[top+1].n = backPtr->n;
    items[back-1].n = frontPtr->n;
}

int quack::itemCount(void)
{
    return count;
}

ostream& operator<<(ostream& out, quack& q)
{
    if ( q.count == 0 ) // No elements have been counted.
    out << endl << "quack: empty" << endl;
    else
    {
        out << endl << "quack: ";
        for ( int i = 0; i < q.count; i++ )
        {
            if ( i < q.count-1 )
                out << q.items[i].n << ", ";
            else out << q.items[i].n;
        }
        out << endl << endl;
    }
    return out;
}

and the header file:

#include <ostream>

using namespace std;

class quack
{
public:
    quack(int capacity);
    ~quack(void);
    bool pushFront(const int n);    // Push an item onto the front.
    bool pushBack(const int n);     // Push an item onto the back.
    bool popFront(int& n);          // Pop an item off the front.
    bool popBack(int& n);           // Pop an item off the back.
    void rotate(int r);             // "rotate" the stored items (see note below).
    void reverse(void);             // Reverse the order of the stored items.
    int itemCount(void);            // Return the current number of stored items.

private:
    int maxSize; // is for the size of the item stack
    int back;    // is for the back or "bottom" of the stack
    int count;   // to count the items added to the stack
    int top;

    struct item                     // Definition of each item stored by the quack.
    {
        int     n;
    };

    item        *items;             // Pointer to storage for the circular array.
    item        *backPtr;
    item        *frontPtr;
    item        *midPtr;
    item        *current;

public:
    friend ostream& operator<<(ostream& out, quack& q);
};

I'm going to be editing this a few times as I read through the code, please forgive me. It appears you're implementing a double-ended queue (dequeue).

Constructor

items    = new item[capacity];
backPtr  = new item;
frontPtr = new item;
midPtr   = new item;
current  = new item;

This doesn't make sense. Your front/back/mid/current pointers don't actually point to one of your items. You probably want to have frontPtr = items+0 and backPtr = items + capacity-1 (or vice versa). Not sure what a dequeue needs a midPtr or current for.

[Edit: It seems item is a struct item { int n } and you're just copying n around. And you have a back index and top index... ]

Destructor

delete frontPtr;
delete backPtr;
delete current;
delete midPtr;
delete [] items; //Heap Corruption Debug Error at the end of program.

Since front/back/etc. should point inside of items, you're double-freeing some of these items. That may be your heap corruption crash. [Edit: or not, considering the weird copying]

items        = NULL;
maxSize  =  0;
back         =  0;

This seems rather silly (the object is about to be no more; who cares?)...

Err, what

Ok, the normal way a simple dequeue would work is to have an array of elements:

items      ->  1   2   3   4   5   6   7   8   9   …
front_ptr  -----------/                   /|\
back_ptr   --------------------------------+

and then you'd have a pointer (frontPtr) which points to the first used spot in the array and another pointer (backPtr) that points to the last used spot. So, a pop_front would do something like this:

if (frontPtr <= backPtr) {
  return *frontPtr++
} else {
  // tried to pop from empty dequeue, handle error
}

That would return 3, and advance front_ptr to point to 4. pop_back would be similar (but with the test reversed, and with -- instead of ++).

Alternatively, instead of pointers, you could store the indices. But pick one, don't use both indices and pointers.

Pointers is one of those things that takes some time to get used to.

When you allocate

items    = new item[capacity]

you effectively have a pointer 'items' pointing to the first element of an array of 'item' allocated on the heap:

items -> {item}{item}{item}..{item}  // capacity

Your other pointers should then point to the same array as well and shouldn't later be used to delete what they point to, instead you just delete 'items':

delete [] items

Arrays and pointers are interchangeable.

items + 1 is the same thing as &items[1] *(items + 1) is the same thing as items[1].

This makes 'current' point to second item in array:

current = items + 1 

This doesn't work because 'current' is a pointer and items[1] is an item object in the array, not an address:

current = items[1]

This works however:

current = &items[1] 

Your main problem is to understand that backptr, frontptr, midptr and current have a different purpose than items. Technically, they all point to some memory location, but semantically items is kind of an anchor for your data container (an array), while the other pointers' purpose is managing that data (book keeping).

So items is the only pointer you should assign memory to you allocate. The other pointers should point into the array (list) items points at.

As a consequence you should set backptr, frontptr, midptr and current to NULL in the c'tor and d'tor and only use new and delete with items.

Btw, I don't know whether your task allows it, but using a linked list instead of an array might make your life easier regarding managing the list (as might using type int for backptr, frontptr, midptr and current).

Two possible causes for the crash when deleting the items array.

  1. The class has no custom copy constructor or assignment operator. This is essential because the built-in ones will be no good. If you ever pass a quack by value as a parameter, or return one from a function, or copy a quack into a variable, you will have two quack s pointing at the same items array. When the second one destructs, it will try to delete the array a second time, and as John Lennon observed in his famous song about deleting heap objects, "No, no no, not a second time".

  2. You do a lot of writing to the items array within your code. If you write past the end (or before the start) of the array, that will be detected when you delete it because at that point the heap implementation will find that you've trashed some management information that it needs to free the block of memory. It's certainly possible that you have a bug in that code, as most people do when they start trying to do this.

In C++ there is an easy way to detect this - don't use a raw array. Use a class that wraps an array and performs bounds checking on every access to it. You can get this with std::vector and using the at function instead of the [] operator (which is not bounds-checked).

valgrind运行它。

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM