简体   繁体   中英

Why this Shaker Sort code doesn't work in C

I'm implementing a generic Shaker Sort algorithm in C and various websites present the code in a way that keeps on giving me segmentation faults and other errors, but it works just fine when using other languages. For example, this code has no issue if I keep it in C# but it stops working after adapting it to C.

This is a full working example of my faithful adaptation of the aforementioned code:

#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// definition of a comparator interface needed by the sort function 
// to compare the values in the array passed as 'void *'
typedef int (*comparator)(void *, void *);

// implementation of the comparator interface for the int type
int int_comparator(void *a, void *b)
{
    int *aa = a;
    int *bb = b;
    return (*aa > *bb) - (*aa < *bb);
}

// generic swap, lacking error checking for the malloc call to keep things brief
void swap(void *a, void *b, size_t size)
{
    unsigned char *aa = a;
    unsigned char *bb = b;
    unsigned char *tmp = malloc(size);

    memcpy(tmp, aa, size);
    memcpy(aa, bb, size);
    memcpy(bb, tmp, size);
    free(tmp);
}

// takes the array, its length, the size of the type it contains, and a pointer 
// to a comparator function according to the type contained in the array
void shaker_sort(void *array, size_t length, size_t size, comparator cmp)
{
    // can't dereference a 'void *', so the array is 
    // now considered as a sequence of raw bytes
    unsigned char *arr = array;
    size_t start = 0;
    size_t end = length - 1;
    int swapped = 1;

    while (swapped) {
        swapped = 0;

        for (size_t i = start; i < end; i++) {
            // since we have a sequence of bytes, access to the original 
            // array elements happens by reading chunks of data of the
            // size of the type contained in the array
            if (cmp(&arr[i * size], &arr[i * size + size]) > 0) {
                swap(&arr[i * size], &arr[i * size + size], size);
                swapped = 1;
            }
        }

        if (!swapped) break;

        swapped = 0;
        end--;

        for (size_t i = end; i >= start; i--) {
            if (cmp(&arr[i * size], &arr[i * size + size]) > 0) {
                swap(&arr[i * size], &arr[i * size + size], size);
                swapped = 1;
            }
        }

        start++;
    }
}

int main(void)
{
    int arr[] = {3, 0, -4, 6, 1};
    size_t length = sizeof(arr) / sizeof(int);

    shaker_sort(arr, length, sizeof(int), int_comparator);

    for (size_t i = 0; i < length; i++) {
        printf("%d ", arr[i]);
    }

    puts("");
}

Compiling with gcc -Wall -Wextra -pedantic -std=c11 test.c -o test is fine, but then it goes into segmentation fault. A quick run of valgrind --tool=memcheck --leak-check=full ./test shows that apparently I'm using uninitialized values, performing invalid reads, and other amenities. For the sake of brevity I'm not including the output but you can just copy the whole code and reproduce my exact results.

Now, the weird thing is that the code works perfectly with a clean valgrind output if I write the second for loop of the Shaker Sort like this:

for (size_t i = end; i > start; i--) {
    if (cmp(&arr[i * size], &arr[i * size - size]) < 0) {
        swap(&arr[i * size], &arr[i * size - size], size);
        swapped = 1;
    }
}

Basically the loop now stops at the element in position start + 1 and, instead of comparing the current element with its successor like before, it compares the current one with its predecessor . And that's it, I haven't got the slightest idea why the code in its pristine form is fine in C# and possibly Java and other languages, but in C it requires this small adjustment. Can somebody shed some light on the matter?

start and i are unsigned,

    for (size_t i = end; i >= start; i--)

first time here start is 0

I counts down to 0 and then subtracting 1 from 0 gets you some other value that being unsigned is either greater-than or equal to zero and the loop continues

do this instead:

    for (size_t i = end; i > start; i--) {
        if (cmp(&arr[i * size - size], &arr[i * size]) > 0) {
            swap(&arr[i * size - size ], &arr[i * size], size);
            swapped = 1;
        }

    }

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