0

i'm trying to make a qsort function from scratch that sorts an array of pointers to structs

this is the code i have right now

static void swap(int *a, int *b) {
  int tmp = *a;
  *a = *b;
  *b = tmp;
}

void _qsort(void* list, int list_len, int left, int right, 
            int(*comp)(const struct shpg_item *a, const struct shpg_item *b)) {
  void *vt, *v3; 
  int i, last, mid = (left + right) / 2; 
  if (left >= right) 
    return; 

  void* vl = (char*)(list + (left * list_len)); 
  void* vr = (char*)(list + (mid * list_len)); 
  swap(vl, vr); 
  last = left; 
  for (i = left + 1; i <= right; i++) { 

    // vl and vt will have the starting address  
    // of the elements which will be passed to  
    // comp function. 
    vt = (char*)(list + (i * list_len)); 
    if ((*comp)(vl, vt) > 0) { 
      ++last; 
      v3 = (char*)(list + (last * list_len)); 
      swap(vt, v3); 
    } 
  } 
  v3 = (char*)(list + (last * list_len)); 
  swap(vl, v3); 
  _qsort(list,list_len, left, last - 1, comp);
  trace_int(1);
  _qsort(list, list_len, last + 1, right, comp); 
}

void list_sort(struct shpg_item **list, int list_len,
               int(*comp)(const struct shpg_item *a, const struct shpg_item *b)) {
  _qsort(*list,list_len,0,(list_len-1),comp);
}

but this gives a segmentation fault error , can any one tell me why and help me ?

mei
  • 35
  • 8
  • Perhaps try mimicking the signature of `qsort` *exactly*. – n. m. could be an AI Jul 19 '20 at 21:46
  • @n.'pronouns'm. and what would that be? i looked all over online but couldn't find a similar implementation – mei Jul 19 '20 at 22:00
  • 1
    You don't start with the implementation. You start with the **documentation**. What is the **signature** of the standard `qsort` function? Use it for your own function. – n. m. could be an AI Jul 19 '20 at 22:07
  • 1
    I cannot understand your algorithm, for instance you do `swap(vl, vr); ` and `swap(vl, v3); ` in any case so without checking first if the elements are in the wrong order, that has no sense . Also `list + (left * list_len)` and other similar expressions are a nice way to go out of the array with an undefined behavior (your segmentation fault error), why do you multiply by *list_length* ? – bruno Jul 19 '20 at 22:07
  • 1
    Your `swap` function swaps two `int`s. But the array being sorted by `qsort` is not an array of `int`s. So treating it as though it were an array of lists is Undefined Behaviour. (That's in addition to the other problems noted.) – rici Jul 19 '20 at 22:17
  • mei: `void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));` – chux - Reinstate Monica Jul 19 '20 at 23:35
  • "but this gives a segmentation fault error " --> Post enough code to replicate the problem. A [mcve] – chux - Reinstate Monica Jul 19 '20 at 23:36
  • Addition in `mid = (left + right) / 2;` may overflow. `mid = left/2 + right/2 + (left%2 + right%2) / 2;` does not. Other alternatives exist too. – chux - Reinstate Monica Jul 19 '20 at 23:39
  • mei, why use `int` in `swap()` when the goal is to swap `struct` pointers? – chux - Reinstate Monica Jul 19 '20 at 23:47
  • I updated my answer to include a working example. – rcgldr Jul 21 '20 at 14:49

3 Answers3

1

void * pointer addition

void * pointer addition is undefined behavior. But since the usual UB is OK, this may or may not be OP's trouble.

void _qsort(void* list, int list_len, int left, ...
    ...
    (list + (left * list_len))  // UB

Instead recommend casting before addition.

// void* vl = (char*)(list + (left * list_len)); 
void* vl = ((char*) list) + (left * list_len); 

Other issues may exist

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • I'm not sure if any other issue exists in the code but you are correct! The pointer value `(list + (left * list_len))` would not be evaluated alone, as the `_qsort` function gets the parameter `list` as a `void *`, an unknown length. Should you add the calling procedure (`&(((char*)list) + (left * list_len)))`) and correction for the swap function decoration to your answer, I'll delete mine. – ssd Jul 20 '20 at 00:45
  • @ssd Let us wait on OP here for a while to see what additional in comes out. Hopefully a [mcve]. – chux - Reinstate Monica Jul 20 '20 at 00:47
  • @chux-ReinstateMonica - I added a working example (using void**) to the middle part of my answer. – rcgldr Jul 21 '20 at 20:05
0

I haven't check the entire code but your swap function seems wrong. Depending on the comment lines in your code;

// vl and vt will have the starting address  
// of the elements which will be passed to  
// comp function. 

if (list + (left * list_len)) and (list + (last * list_len)) are pointers to be swapped (pointers to a string or a struct, for example), your swap function decoration & your caller line should read as:

  1. Swapping two integers, floats, doubles, etc (in general swapping values only):
void swap(int *a, int *b) {
    int t = *a;
    *a = *b;
    *b = t;
}
...
int x = 5;
int y = 3;
swap(&x, &y);
  1. If you need to swap two pointers (a char * string or another type of pointer pointing to a struct), you can just swap pointer values without swapping the content pointed in the actual memory:
void swap(void **a, void **b) {
    void *t = *a;
    *a = *b;
    *b = t;
}
...
char *x = "some string";
char *y = "some other string";
swap(&x, &y);
ssd
  • 2,340
  • 5
  • 19
  • 37
  • 1
    `swap()` for pointer pointing to a struct will not certainly work given the unusual cases where `struct *` not the same size as `void *`. Instead use a `swap()` with using `struct some_struct *` instead of `void *` to achieve "sorts an array of pointers to structs" and portability. – chux - Reinstate Monica Jul 19 '20 at 23:43
  • @rcgldr C allows `void *` of different size than `struct *`. C17dr § 6.2.5 28. Fairly uncommon though. Why think there are always the same: experience? spec? – chux - Reinstate Monica Jul 20 '20 at 20:13
  • @rcgldr Nether had I. Only differences in pointer size I have seen is function pointers vs. object pointers - common enough in 2020. I suspect differences in `void *`, `struct *`, `int *`, etc. are to allow various early memory models. – chux - Reinstate Monica Jul 21 '20 at 09:10
  • @chux-ReinstateMonica - I've since checked C standards, C89, C99. For 32 bit environments, all pointer sizes and `size_t` are 32 bits, for 64 bit environments all pointer sizes and `size_t` are 64 bits. In legacy 16 bit modes, there are near pointers (16 bit) and far pointers (32 bit: segment and offset or if in 286 protected mode, selector and offset). – rcgldr Jul 21 '20 at 13:40
  • @rcgldr Your findings reflect a small sampling of various implementations and not the C spec. Suggest reviewing [Are all data pointers the same size in one platform for all data types?](https://stackoverflow.com/q/1241205/2410359) – chux - Reinstate Monica Jul 21 '20 at 14:10
-1

I've included a working example in the middle part of this answer, and also added an example using qsort.

Taking a quick look at the code I see problem here:

void _qsort(void* list, ...

Since list is an array of pointers it should be:

void _qsort(void** list, ...

or

void _qsort(void* list[], ...

With this declaration, pointer arithmetic will not be an issue, for example, list+3 == &list[3] == pointer to the 3rd pointer in the array. There's no need to cast list, as void** list will work fine in the main part of the code. The only code that will do any casting is the caller's compare function.

You can choose to emulate qsort's compare function parameters using type void **: compare(list+i, list+j), but it would be simpler to use type void *: compare(list[i], list[j]).

Swap should use void** as parameters. The call would be

    swap(list+i, list+j)

/*   ... */

void swap(void **i, void **j){
void * t;
    t = *i;
    *i = *j;
    *j = t;
}

There are some comments about a void pointer possibly having a different size than a struct pointer or any type of data pointer, and that this could cause an issue. If this was true, then the C library function qsort() would not work because the first parameter for qsort is a void pointer, which will result in the caller's pointer being cast to a void pointer. In the caller's compare function, both parameters are const void pointers which the caller's compare function has to cast to the actual pointer types. With qsort() and the caller's compare function, parameters are being cast both to and from void pointers without issue.

C guarantees that a void pointer can be used to hold any type of data pointer, so in essence a void pointer is a generic data pointer (in 16 bit segment or selector environments, a generic "near" data pointer).


This is a working example, using typical Lomuto partition scheme (pivot = a[hi]):

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

typedef struct {
    int  data;
    char name[32];
}XMPL;

int cmpr(void * pi, void *pj)
{
    if(((XMPL *)pi)->data < ((XMPL *)pj)->data)
        return -1;
    if(((XMPL *)pi)->data > ((XMPL *)pj)->data)
        return  1;
    return 0;
}

void swap(void **i, void **j){
void * t;
    t = *i;
    *i = *j;
    *j = t;
}

void QuickSort(void **a, int lo, int hi, int(*cmpp)(void *, void *))
{
void *p;
int i, j;

    while(lo < hi){
        p = a[hi];
        i = lo;
        for(j = lo; j < hi; ++j){
            if((cmpp(a[j], p) < 0)){
                swap(a+i, a+j);
                ++i;
            }
        }
        swap(a+i, a+hi);
        if(i - lo <= hi - i){           /* avoid stack overflow */
            QuickSort(a, lo, i-1, cmpp);
            lo = i+1;
        } else {
            QuickSort(a, i+1, hi, cmpp);
            hi = i-1;
        }
    }
}

#define COUNT (1024)

int main(int argc, char**argv)
{
XMPL *ax;                               /* array of structures */
XMPL **pax;                             /* array of pointers to structures */
int i;

    ax =  malloc(COUNT * sizeof(XMPL));
    pax = malloc(COUNT * sizeof(void **));

    for(i = 0; i < COUNT; i++){         /* init structs, array of ptrs */
        ax[i].data = rand();
        pax[i] = ax+i;
    }

    QuickSort(pax, 0, COUNT-1, cmpr);

    for(i = 1; i < COUNT; i++){
        if(pax[i-1]->data > pax[i]->data){
            break;
        }
    }
    if(i == COUNT)
        printf("passed\n");
    else
        printf("failed\n");
    
    free(pax);
    free(ax);

    return(0);
}

Hoare parition scheme will probably be a bit faster. However, in this case, merge sort should be faster than quick sort. Merge sort does more moves but fewer compares than quick sort, and in this case, only pointers are being moved, while the compare involves an indirection via a pointer and a call to a compare function via a pointer.


Same basic code, but using qsort. Note that the cmpr() function needed one more dereference for each parameter.

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

typedef struct {
    int  data;
    char name[32];
}XMPL;

int cmpr(const void * pi, const void *pj)
{
    if((*(XMPL **)pi)->data < (*(XMPL **)pj)->data)
        return -1;
    if((*(XMPL **)pi)->data > (*(XMPL **)pj)->data)
        return  1;
    return 0;
}

#define COUNT (1024)

int main(int argc, char**argv)
{
XMPL *ax;                               /* array of structures */
XMPL **pax;                             /* array of pointers to structures */
int i;

    ax =  malloc(COUNT * sizeof(XMPL));
    pax = malloc(COUNT * sizeof(void **));

    for(i = 0; i < COUNT; i++){         /* init structs, array of ptrs */
        ax[i].data = rand();
        pax[i] = ax+i;
    }

    qsort(pax, COUNT, sizeof(XMPL *), cmpr);

    for(i = 1; i < COUNT; i++){
        if(pax[i-1]->data > pax[i]->data){
            break;
        }
    }
    if(i == COUNT)
        printf("passed\n");
    else
        printf("failed\n");
    
    free(pax);
    free(ax);

    return(0);
}
rcgldr
  • 27,407
  • 3
  • 36
  • 61
  • @AnttiHaapala - yes, pointer arithmetic with `void *` is undefined, but pointer arithmetic with `void **` is defined and will work fine. – rcgldr Jul 21 '20 at 14:04
  • 1
    But the OP is not sorting `void *` but an array of structure pointers... – Antti Haapala -- Слава Україні Jul 21 '20 at 14:04
  • again Chux already [proved that it was wrong...](https://stackoverflow.com/questions/62985836/how-to-make-a-qsort-function-and-sort-array-of-pointers-to-structs/63015985?noredirect=1#comment111413344_62986585)... – Antti Haapala -- Слава Україні Jul 21 '20 at 14:07
  • Minor: Curious to use `void **` in `pax = malloc(COUNT * sizeof(void **));`. Suggested alternative: `pax = malloc(COUNT * sizeof *pax);` – chux - Reinstate Monica Jul 21 '20 at 20:15
  • @chux-ReinstateMonica - Normally I would use sizeof(*pax) or sizeof(pax[0]), but the point of this was to use type (void **) as a generic type for an array of pointers. – rcgldr Jul 21 '20 at 20:23
  • This answer relies on the common case of `void *` and `struct *` are the same size. Fairly common, but not specified per C. The usual solution involves passing into `QuickSort()` the size of an array element. Just like what `qsort()` does. – chux - Reinstate Monica Jul 21 '20 at 20:23
  • Should sizeof `struct *` differ from `void *` (rare), `for(i = 0; i < COUNT; i++){ ax[i].data = rand(); pax[i] = ax+i; }` fails due to the wrong allocation size. – chux - Reinstate Monica Jul 21 '20 at 20:25
  • If sizeof `void *` differs from `struct *`, there is no issue with `malloc()` returning `void*`. It would not fail when assigned to a `struct *`. Assignment of pointers of different types is not necessarily a binary copy of the pointer's encoding. – chux - Reinstate Monica Jul 21 '20 at 20:26
  • @chux-ReinstateMonica - that would violate the C standard. [void *'s are guaranteed to hold object (i.e. data) pointers](http://c-faq.com/ptrs/generic.html). `void *` is a generic data pointer. Function pointers may have different sizes. – rcgldr Jul 21 '20 at 20:34
  • Sigh, it does not violate. `struct * p = malloc(...);` is going from `void *` to `struct *`. The link discusses going from `struct *` to `void *`. As my comments do not convince, I suggest posting as question for further clarification. – chux - Reinstate Monica Jul 21 '20 at 20:43
  • @chux-ReinstateMonica - I updated my answer to include a second example using qsort and also note that casting is being done in both directions from and to `void *`, wtihout issue. qsort()'s first parameter is a `void *`, which will be compatible with any data pointer type. – rcgldr Jul 21 '20 at 21:40