-1

I've tried the (somewhat questionable) convention of deleteing after usage, but that doesn't seem to work. The program is supposed to receive an input of a single integer, sort a randomly created array, and print elapsed time for sorting, yet when I leave the delete in there, the program abnormally ends without even a warning after I do the input. In other words, it crashes. However, when I comment out just the delete line, the program executes perfectly.

The MWE is measuring time for a simple Quick Sort Algorithm, and since this is a school project I cannot change the main() function and using the QuickSort class and its pointers, etc..

The only things I can change are the stuff that goes in the various functions, and although it does seem that set(double*, int) can just be integrated into the constructor, that's not a possible option here for some reason.

The objective is to define a default double* in the constructor, and then delete it and copy input_array into this->arr in set:

EDIT: I use Windows 10 and the GCC C++ Compiler from MinGw-w64. All compilations have been executed in the Windows Command Prompt.

main.cpp

#include <iostream>
#include <cstdlib> // Just for good measure, though this shouldn't be needed
#include "Sort.hpp"

bool check_quick(QuickSort *quick_sort) {
    int i = 0;
    while(i < (quick_sort->size) - 1) {
        if (quick_sort->arr[i] > quick_sort->arr[i + 1]) break;
        ++i;
    } if (i == (quick_sort->size) - 1) return true;
    else return false;
}

int main() {
    int n; cin >> n;
    double *input_array = new double[n];
    srand((unsigned int)time(NULL));
    for (int k = 0; k < n; k++) input_array[k] = (double)((rand() % n));

    QuickSort* quick_sort = new QuickSort();
    quick_sort->set(input_array, n);
    quick_sort->run();
    if (check_quick(quick_sort)) {
        cout << "QuickSort is validated" << endl << endl;
    } delete quick_sort;
}

Sort.hpp

#define CLOCKS_PER_SECOND 1000
#include <iostream>
#include <ctime>
#include <iomanip> // Use to call setprecision(4)
using namespace std;

class QuickSort {
    friend bool check_quick(QuickSort*); // Give access for private variables
public:
    void print_time() const {
        cout << "QuickSort : " << fixed << setprecision(4) << seconds
             << " sec" << endl;
        // << fixed << setprecision(4) always prints to four numbers after point
    }
    QuickSort() {
        this->arr = new double[10];
        for (int i = 0; i < 10; ++i) this->arr[i - 1] = i; // Set default array
        seconds = clock(); // Set current Millisecond to starting time
    }
    ~QuickSort() {
        delete this->arr; // Delete array in object of this class
    }
    void sorter(double *arr, int begin, int end) { // Sorting Recursive Function
        // Size of array without pivot is: end - begin
        int pivot = arr[end];
            // PIVOT is element at end of subarray "arr[begin...end]"
        int i = begin, j = end;
        while (i <= j) {
            while (arr[i] < pivot) i++; // Increment until arr[i] is larger than
            while (arr[j] > pivot) j--; // Decrement until arr[j] is lesser than
            if (i <= j) { // If the larger element precedes lesser element
                swap(arr[i], arr[j]); // Call Swap function
                i++; j--;
            } // If i is larger than j now, i was 1 lesser than j before,
              // effectively leaving no more elements to scan.
        }
        if (begin < j) sorter(this->arr, begin, j); // Recursive, larger part
        if (end > i) sorter (this->arr, i, end); // Recursive, lesser part
    }
    void run() {
        sorter(this->arr, 0, this->size - 1); // Call Sorter function
        seconds = (double)(clock() - seconds) / (double)(CLOCKS_PER_SECOND);
            // Calculate Difference of Ticks and divide by Ticks per second.
            // Now, `seconds` is passed seconds with millisecond precision.
    }
    void set(double *arr, int size) {
        this->arr = new double[size]; // Make new array of `size` size
        for (int i = 0; i < size; i++) this->arr[i] = arr[i]; // Copy input_arr
        for (int i = 0; i < size; i++) cout << this->arr[i] << endl; // Copy input_arr
        this->size = size; // Save global `size` to object of class
    }
    void swap(double &p, double &q) { // Swap Function
                                      // Ampersand precedence to change input
        double x = p; // Temporary `double` saver
        p = q; // p is q
        q = x; // q is x, which is p
    }
private:
    double *arr;
    int size;
    double seconds;
};
Paul Kim
  • 1,153
  • 1
  • 9
  • 17
  • 2
    Why does `quick_sort` need to be dynamically allocated? Or a pointer at all for that matter? Also naming a parameter in a member function the same as a member variable is asking for trouble. – scohe001 Apr 16 '19 at 15:50
  • 5
    Side note : whenever you're no longer bound by stupid professor restrictions just use unique ptr instead of bare pointers or std vector for dynamic allocated arrays. – Hatted Rooster Apr 16 '19 at 15:50
  • 1
    `QuickSort* quick_sort = new QuickSort();` You should not dynamically allocate your QuickSort object. – drescherjm Apr 16 '19 at 15:50
  • 2
    the convention is rather to use no `delete` at all, at least not directly – 463035818_is_not_an_ai Apr 16 '19 at 15:52
  • @scohe001 "bound by stupid professors..." and frankly, none of the students understand the need for it to be a pointer at all. – Paul Kim Apr 16 '19 at 15:53
  • Modern `c++` removes the need for manual memory management. – drescherjm Apr 16 '19 at 15:54
  • 2
    @SombreroChicken why wait for it until it is too late? Writing your own simple version of a smart pointer is not too difficult and should be lecture number zero instead of this "you have to use raw pointers in order to learn it the hard way" non-sense – 463035818_is_not_an_ai Apr 16 '19 at 15:54
  • @drescherjm Would dynamic allocation create complications? – Paul Kim Apr 16 '19 at 15:54
  • 1
    @user463035818 Hm, maybe I should change that, guess it reflects the "professor's perception of" the convention... – Paul Kim Apr 16 '19 at 15:55
  • 1
    It makes the problem harder than it needs to be and discourages new people to the language because of the unnecessary complexity taught at the wrong time. There is a very good youtube lecture / talk on the topic. Although I don't have a link. – drescherjm Apr 16 '19 at 15:56
  • 4
    @user463035818 Very true, that's another good option. I'm so done with universities teaching students C instead of C++. – Hatted Rooster Apr 16 '19 at 15:56
  • 2
    @drescherjm You probably mean https://youtu.be/YnWhqhNdYyk – Hatted Rooster Apr 16 '19 at 16:15
  • About [using namespace std](https://stackoverflow.com/questions/1452721/why-is-using-namespace-std-considered-bad-practice)... – Aconcagua Apr 16 '19 at 16:16

3 Answers3

3

In your QuickSort constructor you are writing outside the bounds of the array that you are allocating:

this->arr = new double[10];
for (int i = 0; i < 10; ++i) this->arr[i - 1] = i; // Set default array

In the first iteration i is 0 so this->arr[i - 1] writes to the element -1. This is only crashing when you call delete as if you don't the runtime doesn't notice this corruption and exits cleanly.

Presumably just changing i-1 to i will produce the desired behaviour.

Alan Birtles
  • 32,622
  • 4
  • 31
  • 60
  • Took me ages to find it too, just after I found it I noticed the new green squiggly line in VS 2019 which said "index of array is -1" – Alan Birtles Apr 16 '19 at 17:49
2

The first line of QuickSort::set:

this->arr = new double[size]; // Make new array of `size` size

leaks the array allocated in QuickSort's constructor. You need to delete[] that array first, before assigning arr to point to another one:

delete[] arr;
this->arr = new double[size]; // Make new array of `size` size

If this were the real world, and not a shcool assignment, it would be much better not to use a raw pointer in this case. Rather, a std::vector<double> would be much more appropriate.

Miles Budnek
  • 28,216
  • 2
  • 35
  • 52
  • I just tried this solution as well, but the program still doesn't work. There's no output produced. – Paul Kim Apr 16 '19 at 16:01
  • You should edit your question to include more information about your problem. This avoids the obvious memory leak, since that's what the question's title asks about. – Miles Budnek Apr 16 '19 at 16:04
  • I don't know what other details I can provide; Just commenting out the `delete[]` line does really work, as in sorting and measuring time. – Paul Kim Apr 16 '19 at 16:11
  • 1
    @K.Paul Perhaps Eric Lippert's [How to Debug Small Programs](https://ericlippert.com/2014/03/05/how-to-debug-small-programs/) article will be of help to you. – Miles Budnek Apr 16 '19 at 16:16
  • I'd consider this is rather a comment than an answer; a memory leak won't provoke crash of a programme – unless we leaked that much memory that OS cannot provide us with more, but I doubt pretty much we got at this point. Still a good catch. – Aconcagua Apr 16 '19 at 16:22
  • @Aconcagua The original question didn't mention a crash. It only mentioned a memory leak, so I answered as such. – Miles Budnek Apr 16 '19 at 16:23
2

A quick look reveals that you are using delete instead of delete[]..

this line:

delete this->arr; // Delete array in object of this class

should be:

delete[] this->arr; // Delete array in object of this class

Additionally, as per the convention of delete you should also do this:

delete[] this->arr;
this->arr = nullptr; // This is important, else your are double deleting which is calling for trouble.

subdeveloper
  • 1,381
  • 8
  • 21
  • I tried that too, but the problem doesn't work even with that change. – Paul Kim Apr 16 '19 at 15:56
  • @K.Paul Can you define "doesn't work"? What exactly fails? Do you get the wrong output? Does it crash? – Miles Budnek Apr 16 '19 at 15:59
  • Crashes, exactly came symptoms as before: no output produced. – Paul Kim Apr 16 '19 at 16:02
  • Works after the `this->arr = nullptr`! Thank you. – Paul Kim Apr 16 '19 at 16:03
  • No...sorry, false alarm, I retried and it still produces the same problem. – Paul Kim Apr 16 '19 at 16:08
  • `this->arr = nullptr` in a *destructor* is totally obsolete, as `arr` (here!) is an ordinary pointer, not a smart pointer. So there won't be any double deletion either, and the variable runs out of scope after exiting the destructor anyway. – Aconcagua Apr 16 '19 at 16:14
  • @Aconcagua you're correct. I missed its inside a Destructor. I was talking more in terms of delete convention he was talking about. – subdeveloper Apr 16 '19 at 16:16
  • @K.Paul, finally to save everyone's time, I tried to execute it on my box. It runs without crashes. Can you specify which OS/Compiler you are using.. And also please provide code for function `check_quick` – subdeveloper Apr 16 '19 at 16:18
  • 1
    OK.. i do not have the environment that you are running. But I have slightly formatted the code for you so its easier to debug. Here's the code https://pastebin.com/2WL54z3F – subdeveloper Apr 16 '19 at 16:40