Search code examples
c++segmentation-faultquicksort

Segmentation fault with Quick sort


I need to implement a quick sort for my university assignment. The following is the implementation for quick sort that I wrote in c++:


#include <iostream>
#include <algorithm>
#include <cstddef>
#include <utility>

template<typename T, std::size_t S>
void quick_forward_order(T(&container)[S], std::size_t lower_bound = std::size_t(0), std::size_t upper_bound = S - 1)
{
    if(lower_bound >= upper_bound)
    {
        return;
    }

    // Choosing the last element as the pivot
    const T& pivot_element = container[upper_bound];

    std::size_t rhs_pointer = upper_bound;
    std::size_t lhs_pointer = lower_bound;

    while (lhs_pointer < rhs_pointer)
    {
        while(container[lhs_pointer] <= pivot_element && lhs_pointer < rhs_pointer)
        {
            ++lhs_pointer;
        }

        while(container[rhs_pointer] >= pivot_element && rhs_pointer > lhs_pointer)
        {
            --rhs_pointer;
        }

        std::swap(
            container[lhs_pointer],
            container[rhs_pointer]
        );
    }

    std::swap(
        container[lhs_pointer],
        container[upper_bound]
    );

    quick_forward_order(container, lower_bound, lhs_pointer - 1);
    quick_forward_order(container, lhs_pointer + 1, upper_bound);
};

template<typename T, std::size_t S>
void quick_reverse_order(T(&container)[S], std::size_t lower_bound = std::size_t(0), std::size_t upper_bound = S - 1)
{
    if(lower_bound >= upper_bound)
    {
        return;
    }

    // Choosing the last element as the pivot
    const T& pivot_element = container[upper_bound];

    std::size_t rhs_pointer = upper_bound;
    std::size_t lhs_pointer = lower_bound;

    while (lhs_pointer < rhs_pointer)
    {
        while(container[lhs_pointer] >= pivot_element && lhs_pointer < rhs_pointer)
        {
            ++lhs_pointer;
        }

        while(container[rhs_pointer] <= pivot_element && rhs_pointer > lhs_pointer)
        {
            --rhs_pointer;
        }

        std::swap(
            container[lhs_pointer],
            container[rhs_pointer]
        );
    }

    std::swap(
        container[lhs_pointer],
        container[upper_bound]
    );

    quick_reverse_order(container, lower_bound, lhs_pointer - 1);
    quick_reverse_order(container, lhs_pointer + 1, upper_bound);
};

This is the code that I tried and tested this with:


int main(void)
{
    int array[5] = {3, 1, 5, 7, 6};
    
    std::cout << "Before sorting:" << "\n\n";
    
    for(std::size_t index = 0; index < 5; ++index)
    {
        std::cout << array[index] << "\n";
    }
    
    std::cout << std::flush;
    
    
    quick_forward_order(array);
    
    
    std::cout << "After sorting:" << "\n\n";
    
    for(std::size_t index = 0; index < 5; ++index)
    {
        std::cout << array[index] << "\n";
    }
    
    std::cout << std::flush;
    
    return (0);
}

I keep getting a segmentation fault around this line for each of the methods:

while(container[lhs_pointer] <= pivot_element && lhs_pointer < rhs_pointer)

I can't figure out what the problem is though. Does it have to do with the fact that I am using an std::size_t and it goes into the negatvie? If so, then how can I fix this please?


Solution

  • Here is the full debug log:

    Process 12582 stopped
    * thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=2, address=0x1d6fdfe67c)
        frame #0: 0x0000000100003148 test`void quick_forward_order<int, 5ul>(container=0x000000016fdfe7c0, lower_bound=0, upper_bound=12884901893) [5ul], unsigned long, unsigned long) at test.cpp:26:12
       23         ++lhs_pointer;
       24       }
       25
    -> 26       while (container[rhs_pointer] >= pivot_element &&
       27              rhs_pointer > lhs_pointer) {
       28         --rhs_pointer;
       29       }
    Target 0: (test) stopped.
    

    You can see access violation because upper_bound is too big, and your guess is correct. Once a type of size_t of value 0 decrease by 1, its value will be set to the largest integer value that can be represented in that large space.

    And if we continue debug:

    (lldb) bt <============ print calling sequence, which is backtrace(bt)
    * thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=2, address=0x1d6fdfe67c)
      * frame #0: 0x0000000100003148 test`void quick_forward_order<int, 5ul>(container=0x000000016fdfe7c0, lower_bound=0, upper_bound=12884901893) [5ul], unsigned long, unsigned long) at test.cpp:26:12
        frame #1: 0x00000001000031f0 test`void quick_forward_order<int, 5ul>(container=0x000000016fdfe7c0, lower_bound=0, upper_bound=1) [5ul], unsigned long, unsigned long) at test.cpp:36:3
        frame #2: 0x00000001000031f0 test`void quick_forward_order<int, 5ul>(container=0x000000016fdfe7c0, lower_bound=0, upper_bound=2) [5ul], unsigned long, unsigned long) at test.cpp:36:3
        frame #3: 0x00000001000031f0 test`void quick_forward_order<int, 5ul>(container=0x000000016fdfe7c0, lower_bound=0, upper_bound=4) [5ul], unsigned long, unsigned long) at test.cpp:36:3
        frame #4: 0x0000000100002f1c test`main at test.cpp:86:3
        frame #5: 0x000000018ed2d058 dyld`start + 2224
    

    Let's check frame 3, which is the first time you call quick_forward_order:

    (lldb) f 3 <======= frame 3
    frame #3: 0x00000001000031f0 test`void quick_forward_order<int, 5ul>(container=0x000000016fdfe7c0, lower_bound=0, upper_bound=4) [5ul], unsigned long, unsigned long) at test.cpp:36:3
       33
       34     std::swap(container[lhs_pointer], container[upper_bound]);
       35
    -> 36     quick_forward_order(container, lower_bound, lhs_pointer - 1);
       37     quick_forward_order(container, lhs_pointer + 1, upper_bound);
       38   };
       39
    (lldb) p lhs_pointer <==== print lhs_pointer
    (size_t) $0 = 3
    

    And then frame 2 and 1:

    (lldb) f 2 <======== frame 2
    frame #2: 0x00000001000031f0 test`void quick_forward_order<int, 5ul>(container=0x000000016fdfe7c0, lower_bound=0, upper_bound=2) [5ul], unsigned long, unsigned long) at test.cpp:36:3
       33
       34     std::swap(container[lhs_pointer], container[upper_bound]);
       35
    -> 36     quick_forward_order(container, lower_bound, lhs_pointer - 1);
       37     quick_forward_order(container, lhs_pointer + 1, upper_bound);
       38   };
       39
    (lldb) p lhs_pointer <===== print lhs_pointer
    (size_t) $1 = 2      <===== start decreasing because we pass lhs_pointer - 1
    (lldb) f 1           <===== frame 1
    frame #1: 0x00000001000031f0 test`void quick_forward_order<int, 5ul>(container=0x000000016fdfe7c0, lower_bound=0, upper_bound=1) [5ul], unsigned long, unsigned long) at test.cpp:36:3
       33
       34     std::swap(container[lhs_pointer], container[upper_bound]);
       35
    -> 36     quick_forward_order(container, lower_bound, lhs_pointer - 1);
       37     quick_forward_order(container, lhs_pointer + 1, upper_bound);
       38   };
       39
    (lldb) p lhs_pointer
    (size_t) $2 = 0
    

    Ops. You can see lhs_pointer is 0 now. Once you pass it as lhs_pointer - 1.... Now everything is clear.

    And as for solution, that's really simple: swap size_t with int in your interface. Yes, that's all:

    template <typename T, std::size_t S>
    void quick_forward_order(T (&container)[S], int lower_bound = 0,
                             int upper_bound = S - 1)
    

    And the protection at the beginning of your function will start to work.

    ❯ g++ -std=c++11 test.cpp -o test
    ❯ ./test
    Before sorting:
    
    3
    1
    5
    7
    6
    After sorting:
    
    1
    3
    5
    6
    7
    

    If you really want a size_t implementation, that's easy ,too: Before you call quick_forward_order function, judge if lhs_pointer is 0, if it is, stop calling quick_forward_order