Search code examples
cfunctionvoid-pointersqsort

Sorting structures with qsort and void pointer issues


My task is to read data that's stored in string and double pairs, have the user input what it should be sorted by (in the form "n-v+") and then sort it with qsort. I'm having issues with writing my comparison function.

I'm getting the following errors:

  • warning: dereferencing ‘void *’ pointer
  • error: void value not ignored as it ought to be
  • warning: control reaches end of non-void function [-Wreturn-type]

Here's the part of code with the function

typedef struct {
char name[32];
double value;
} record;

int sign1=0; //1 is +, -1 is -
int tip1=0; //1 is n, 2 is v
int sign2=0; //1 is +, -1 is -
int tip2=0; //1 is n, 2 is v

//comparison function
int compa(const void*p,const void*d){ //1 means first, -1 means second
    int result=0;
    record first;
    first=*p;
    record second;
    second=*d;
    if(tip1==1){ //compare for n first
        result=strcmp(first.name,second.name);  //first goes smaller
        if(result!=0) return sign1*result;
        else { //then for v
            if (first.value>second.value)result=-1; 
            else result=1;  
            return sign2*result;
            }
        }
    else if (tip1==2){//compare for v first
        if (first.value>second.value){
            result=-1; 
            return sign1*result;}
        else if (first.value<second.value) {
            result=1;
            return sign1*result;}
        else{ //then for n
            result=strcmp(first.name,second.name);  //first goes smaller
            if(result!=0) return sign2*result;
            else return sign2;
            }
        }   
}

Solution

  • How to do it?

    I think it's probably best to write two pairs of functions. In each pair, one will be trivial, negating the result of calling the other one in the pair. For example, you have 'name ascending' and 'name descending'; you will probably implement 'name ascending' in full with prototype int cmp_name_asc(const void *p1, const void *p2);, and then implement the other in the pair as:

    int cmp_name_des(const void *p1, const void *p2)
    {
        return -cmp_name_asc(p1, p2);
    }
    

    and similarly for the pair of cmp_value_asc and cmp_value_des.

    int cmp_value_des(const void *p1, const void *p2)
    {
        return -cmp_value_asc(p1, p2);
    }
    

    If you're really antsy about the mild difference in overhead, you can work around it with a static inline function that you call twice, once each in cmp_value_des() and cmp_value_asc(), but that is almost certainly not worthwhile.

    That then leaves you with writing two functions:

    int cmp_value_asc(const void *p1, const void *p2)
    {
        const record *r1 = p1;
        const record *r2 = p2;
        if (r1->value < r2->value)
            return -1;
        else if (r1->value > r2->value)
            return +1;
        else
            return 0;
    }
    
    int cmp_name_asc(const void *p1, const void *p2)
    {
        const record *r1 = p1;
        const record *r2 = p2;
        return strcmp(r1->name, r2->name);
    }
    

    Then, when you're parsing the arguments, you control which of the four functions is called. This is much easier that futzing with those global variables, and making copies of the structures (avoid that whenever possible) and so on.

    Why the errors?

    You were getting the 'warning: dereferencing ‘void *’ pointer' messages because you have:

    record first;
    first=*p;
    

    You'd need to use:

    record first = *(const record *)p;
    

    to get the right copying behaviour — convert the void * to the correct pointer type and then dereference it. Except this copies the information; you shouldn't do that (it slows things down wholly unnecessarily). You should be using:

     const record *firstp = (const record *)p;
    

    except that the cast is not crucial in C (it would be in C++, if you were careless enough to sort this way in your C++ code — that would be a bad idea, too). Note too that this makes the initialization part of the variable definition, rather than as separate assignment later. That's usually a good technique — especially in C++.

    Similarly, you were probably getting the 'error: void value not ignored as it ought to be' errors on the same lines. Since p is a void *, *p is a void, and you were trying to use the result of a void value — which you can't do.

    The 'warning: control reaches end of non-void function [-Wreturn-type]' warning is because your comparison function has the structure:

    if (tip1 == 1)
    {
        …code that returns a value…
    }
    else if (tip1 == 2)
    {
        …code that returns a value…
    }
    

    and because there is neither an else clause nor a return 0; nor anything else after the else if block, the function can, in principle, exit without returning a value, which is what the warning tells you.

    I'll also observe you don't seem to use tip2 (should tip1 == 2 be tip2 != 0?).

    Is the code supposed to sort on name and value?

    On further scrutiny, it seems like you might be intending to have two part comparisons, with sorting by name first and value second, or value first and name second, and for each of those comparisons, either ascending or descending. Is that correct?

    If so, you can still create the small functions shown before, but make them static since you won't be using them outside the comparison code. You can then decide to create function pointers to do the work:

    static int (*compare1)(const void *p1, const void *p2) = cmp_name_asc;
    static int (*compare2)(const void *p1, const void *p2) = cmp_value_des;
    

    Your parsing code will select the correct function names for assignment to compare1 and compare2. Your comparison function then becomes:

    int cmpa(const void *p1, const void *p2)
    {
        int rc = (*compare1)(p1, p2);        // Or int rc = compare1(p1, p2);
        if (rc == 0)
            rc = (*compare2)(p1, p2);
        return rc;
    }
    

    It's a mild nuisance to have to use the file scope variables compare1 and compare2, but much less of a nuisance than trying to do it all inline as in the code in the question.

    It is pretty easy to extend this to sort by three criteria, or 1..N criteria (using an array of pointers to functions, and setting a pointer to null to indicate no more comparisons). The hardest part is setting the correct function pointers in compare1 and compare2 and equivalents.

    Working code

    Here is code showing most of the suggestions above (I'm not fussed about the minimal performance lost in the descending comparator functions):

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    typedef struct
    {
        char name[32];
        double value;
    } record;
    
    static int cmp_value_asc(const void *p1, const void *p2)
    {
        const record *r1 = p1;
        const record *r2 = p2;
        if (r1->value < r2->value)
            return -1;
        else if (r1->value > r2->value)
            return +1;
        else
            return 0;
    }
    
    static int cmp_name_asc(const void *p1, const void *p2)
    {
        const record *r1 = p1;
        const record *r2 = p2;
        return strcmp(r1->name, r2->name);
    }
    
    static int cmp_name_des(const void *p1, const void *p2)
    {
        return -cmp_name_asc(p1, p2);
    }
    
    static int cmp_value_des(const void *p1, const void *p2)
    {
        return -cmp_value_asc(p1, p2);
    }
    
    static int (*compare1)(const void *p1, const void *p2) = cmp_name_asc;
    static int (*compare2)(const void *p1, const void *p2) = cmp_value_des;
    
    static int cmpa(const void *p1, const void *p2)
    {
        int rc = (*compare1)(p1, p2);
        if (rc == 0)
            rc = (*compare2)(p1, p2);
        return rc;
    }
    
    static void dump_records(const char *tag, int num, const record *data);
    
    int main(void)
    {
        record data[] =
        {
            /*
            random -n 15 -c -T '{ "%C%v%2:8w", %[20:100]f },' |
            awk '{ printf("%8s%s %-14s %5.2f %s\n", "", $1, $2, $3, $4, $5) }'
            Plus three lines duplicated on name and then change the value;
            plus three lines duplicated on value and then change the name.
            */
            { "Memrgi",      66.90 },
            { "Joeeeoahnm",  98.40 },
            { "Turner",      81.40 },
            { "Rebfno",      81.40 },
            { "Rebfno",      23.19 },
            { "Tecao",       66.30 },
            { "Guvoejnard",  62.40 },
            { "Zipnoib",     32.70 },
            { "Kerjruw",     49.60 },
            { "Vebin",       51.60 },
            { "Ghost",       51.60 },
            { "Reoe",        85.00 },
            { "Yepfenen",    84.60 },
            { "Yepfenen",    82.60 },
            { "Kopl",        94.80 },
            { "Soorwzeo",    15.40 },
            { "Soorwzeo",    85.40 },
            { "Nemigiat",    29.10 },
            { "Poisson",     79.40 },
            { "Sositpv",     79.40 },
            { "Giidahroet",  71.00 },
        };
        enum { NUM_DATA = sizeof(data) / sizeof(data[0]) };
    
        dump_records("Before sorting", NUM_DATA, data);
    
        compare1 = cmp_name_des;
        compare2 = cmp_value_asc;
        qsort(data, NUM_DATA, sizeof(data[0]), cmpa);
        dump_records("After sorting by name descending, value ascending", NUM_DATA, data);
    
        compare1 = cmp_value_des;
        compare2 = cmp_name_asc;
        qsort(data, NUM_DATA, sizeof(data[0]), cmpa);
        dump_records("After sorting by value descending, name ascending", NUM_DATA, data);
    
        compare1 = cmp_value_asc;
        compare2 = cmp_name_asc;
        qsort(data, NUM_DATA, sizeof(data[0]), cmpa);
        dump_records("After sorting by value ascending, name ascending", NUM_DATA, data);
    
        compare1 = cmp_name_asc;
        compare2 = cmp_value_asc;
        qsort(data, NUM_DATA, sizeof(data[0]), cmpa);
        dump_records("After sorting by name ascending, value ascending", NUM_DATA, data);
    
        return 0;
    }
    
    static void dump_records(const char *tag, int num, const record *data)
    {
        printf("%s (%d):\n", tag, num);
        for (int i = 0; i < num; i++)
            printf("%2d: %-12s %5.2f\n", i + 1, data[i].name, data[i].value);
    }
    

    Output from that program:

    Before sorting (21):
     1: Memrgi       66.90
     2: Joeeeoahnm   98.40
     3: Turner       81.40
     4: Rebfno       81.40
     5: Rebfno       23.19
     6: Tecao        66.30
     7: Guvoejnard   62.40
     8: Zipnoib      32.70
     9: Kerjruw      49.60
    10: Vebin        51.60
    11: Ghost        51.60
    12: Reoe         85.00
    13: Yepfenen     84.60
    14: Yepfenen     82.60
    15: Kopl         94.80
    16: Soorwzeo     15.40
    17: Soorwzeo     85.40
    18: Nemigiat     29.10
    19: Poisson      79.40
    20: Sositpv      79.40
    21: Giidahroet   71.00
    After sorting by name descending, value ascending (21):
     1: Zipnoib      32.70
     2: Yepfenen     82.60
     3: Yepfenen     84.60
     4: Vebin        51.60
     5: Turner       81.40
     6: Tecao        66.30
     7: Sositpv      79.40
     8: Soorwzeo     15.40
     9: Soorwzeo     85.40
    10: Reoe         85.00
    11: Rebfno       23.19
    12: Rebfno       81.40
    13: Poisson      79.40
    14: Nemigiat     29.10
    15: Memrgi       66.90
    16: Kopl         94.80
    17: Kerjruw      49.60
    18: Joeeeoahnm   98.40
    19: Guvoejnard   62.40
    20: Giidahroet   71.00
    21: Ghost        51.60
    After sorting by value descending, name ascending (21):
     1: Joeeeoahnm   98.40
     2: Kopl         94.80
     3: Soorwzeo     85.40
     4: Reoe         85.00
     5: Yepfenen     84.60
     6: Yepfenen     82.60
     7: Rebfno       81.40
     8: Turner       81.40
     9: Poisson      79.40
    10: Sositpv      79.40
    11: Giidahroet   71.00
    12: Memrgi       66.90
    13: Tecao        66.30
    14: Guvoejnard   62.40
    15: Ghost        51.60
    16: Vebin        51.60
    17: Kerjruw      49.60
    18: Zipnoib      32.70
    19: Nemigiat     29.10
    20: Rebfno       23.19
    21: Soorwzeo     15.40
    After sorting by value ascending, name ascending (21):
     1: Soorwzeo     15.40
     2: Rebfno       23.19
     3: Nemigiat     29.10
     4: Zipnoib      32.70
     5: Kerjruw      49.60
     6: Ghost        51.60
     7: Vebin        51.60
     8: Guvoejnard   62.40
     9: Tecao        66.30
    10: Memrgi       66.90
    11: Giidahroet   71.00
    12: Poisson      79.40
    13: Sositpv      79.40
    14: Rebfno       81.40
    15: Turner       81.40
    16: Yepfenen     82.60
    17: Yepfenen     84.60
    18: Reoe         85.00
    19: Soorwzeo     85.40
    20: Kopl         94.80
    21: Joeeeoahnm   98.40
    After sorting by name ascending, value ascending (21):
     1: Ghost        51.60
     2: Giidahroet   71.00
     3: Guvoejnard   62.40
     4: Joeeeoahnm   98.40
     5: Kerjruw      49.60
     6: Kopl         94.80
     7: Memrgi       66.90
     8: Nemigiat     29.10
     9: Poisson      79.40
    10: Rebfno       23.19
    11: Rebfno       81.40
    12: Reoe         85.00
    13: Soorwzeo     15.40
    14: Soorwzeo     85.40
    15: Sositpv      79.40
    16: Tecao        66.30
    17: Turner       81.40
    18: Vebin        51.60
    19: Yepfenen     82.60
    20: Yepfenen     84.60
    21: Zipnoib      32.70