Chris
Chris

Reputation: 95

qsort comparator segmentation fault

I am writing a library of data structures in C which I plan to use for a personal project (I do realize there are generic libraries available, but I thought this would be a great learning experience). In so doing, I have created a data structure which is quite similar to the built in Python list implementation (in terms of exposed operations).

I was writing some unit tests for this structure when I stumbled on some difficulty with the qsort comparator function. I've crawled through tons of SO responses about the same issue and none of the recommended fixes seemed to work.

Relevant code given below (other code omitted due to length, but I'd be happy to bring it in if it may be needed):

typedef struct glist glist;

struct glist {
    void **data;
    size_t len;
    size_t cap;
    int (*cmp)(void const*, void const*);
    void (*free)(void*);
};

static int list_test_comparator(const void *left, void const *right);

glist* glist_new(int (*cmpfn)(const void*, const void*), void (*freefn)(void*)) {
    glist *list = malloc(sizeof(glist));
    if (!list) {
        return NULL;
    }

    size_t cap = sizeof(void*) * 10;
    list->data = calloc(cap, cap);
    if (!list->data) {
        free(list);
        return NULL;
    }

    list->len = 0;
    list->cap = 10;
    list->cmp = cmpfn;
    list->free = freefn;
    return list;
}

void glist_sort(glist *list) {
    if ((!list) || (list->len == 0)) {return;}
    qsort(list->data, list->len, sizeof(void *), list->cmp);
}

static int list_test_comparator(const void *left, const void *right) {
    const char *l = left;
    const char *r = right;
    printf("Left: %s %p\n", l, left);
    printf("Right: %s %p\n", r, right);
    int res = strcmp(l, r);
    printf("Res: %d\n", res);
    return res;
}

/* Run by CUnit before and after each test case */
void list_test_setup(void) {
    list_test = glist_new(list_test_comparator, free);
}

void list_test_sort(void) {
    for (int i = 0; i < 15; i++) {
        char *some = "%d";
        char *next = malloc(20);
        CU_ASSERT_FATAL(next != NULL);

        sprintf(next, some, rand());
        CU_ASSERT(glist_append(list_test, next) == true);
    }

    /* Note that this is a CUnit test with setup function creating 
     * the structure with the above defined list_test_comparator func */
    glist_sort(list_test);

    int val;
    int prev = -1; /* rand() should always return value between 0 and RAND_MAX */
    for (int i = 0; i < 15; i++) {
        char *test = glist_get(list_test, i);
        sscanf(test, "%*s %d", &val);
        CU_ASSERT(val <= prev);
        prev = val;
    }
}

As I mentioned above, I have tried a number of suggestions in similar questions (dereferencing pointers on cast, changing my call to qsort, etc). The dereferencing trick const char *l = *(const char**)left; results in a segfault (and Valgrind entering an infinite loop).

I even got to the point (which you can see some of in the comparator above) where I started printing out pointer locations before and within the comparator and I found most of the pointer locations are not the same.

Partial output:

/* Before sort */
282475249 0x7fb629c04f30
1622650073 0x7fb629c04f50
984943658 0x7fb629c04f70
1144108930 0x7fb629c04f90
470211272 0x7fb629c04fb0
101027544 0x7fb629c04fd0
1457850878 0x7fb629c04ff0
1458777923 0x7fb629c05010
2007237709 0x7fb629c05030
823564440 0x7fb629c05050
1115438165 0x7fb629c05200
1784484492 0x7fb629c053b0
74243042 0x7fb629c053d0
114807987 0x7fb629c053f0

/* In comparator */
Left: O?)? 0x7fb629c05070
Right: O?)? 0x7fb629c050a8
Res: -224
Left: ?O?)? 0x7fb629c050a8
Right: ?O?)? 0x7fb629c050e0
Res: -4
Left: 0O?)? 0x7fb629c05078
Right: 0O?)? 0x7fb629c05070
Res: -192

My best guess for the problem involves the fact that somehow qsort isn't getting the right information about what's in my internal data structure, so it is giving me incorrect pointer offsets.

Other relevant information:

Edit: Show some more code.

Upvotes: 2

Views: 457

Answers (1)

juhist
juhist

Reputation: 4314

I think you're misunderstanding what the comparator receives pointers to. It receives a pointer to the memory holding the data. So, if your data is void*, it receives really void** pointers masqueraded as void*.

To fix this, try the following code:

static int list_test_comparator(const void *left, const void *right) {
    const void *leftptr = *(const void**)left;
    const void *rightptr = *(const void**)right;
    const char *l = leftptr;
    const char *r = rightptr;
    printf("Left: %s %p\n", l, left);
    printf("Right: %s %p\n", r, right);
    int res = strcmp(l, r);
    printf("Res: %d\n", res);
    return res;
}

I can't now test my code as glist_append and glist_get are missing from your code but that's the way qsort should be used.

Upvotes: 3

Related Questions