const qualifier in qsort compare function

Viewed 113

I have this to sort a string array (argv itself) in two ways with qsort. i started with the linux man 3 qsort example. There cmpstringp is only one line:

 /* The actual arguments to this function are "pointers to
    pointers to char", but strcmp(3) arguments are "pointers
    to char", hence the following cast plus dereference */

 return strcmp(*(const char **) p1, *(const char **) p2);

I tried to reformulate this and it compiles without warnings only with

char *const* sp1 = vep1

i.e. const belongs in the middle - it is the argv entry (?). At least that is what qsort declaration demands. The above cast from man page seems different and complicated.

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

/* vep: void pointer to elements being qsorted 
   sp: string pointer (char **)            */
int 
cmpstringp(const void *vep1, const void *vep2) {

    char *const *sp1 = vep1,
         *const *sp2 = vep2;

    return strcmp(*sp1, *sp2);
}
/* Make char* from void*, and even char    */
int 
cmpchar(const void *p1, const void *p2) {

    const char *cp1 = p1,
               *cp2 = p2;

    char c1 = *cp1,
         c2 = *cp2;

    if (c1 == c2)
        return 0;
    else
        return c1 > c2 ? 1 : -1;
}

/* Sort cmd line args in two ways with qsort */
int main(int argc, char **argv) {

    /* sort chars of each string */
    for (int i = 1; i < argc; i++)
    qsort(argv[i],                  // base / first  
          strlen(argv[i]), 1,       // n_elems, elem_size
          cmpchar);

    /* sort argv strings 1 to argc-1 */
    qsort(argv + 1,
          argc - 1, sizeof *argv,
          cmpstringp);

    for (int j = 1; j < argc; j++)
        puts(argv[j]);

    return 0;
}

The second cmp function cmpchar has one extra assignment level. Without there would be just some harmless stars like if (*cp1 == *cp2).

How do I sort the elements of argv in C? has a solution like man page, with a cast directly in strcmp().

But isn't my cast-free approach more correct? I got warnings until I put the const where it belongs.

2 Answers

The const in the middle of char *const *sp1 = vep1 means that whatever is designated by *sp1 is const. This is required because the const void * says that the memory directly pointed to by the pointer is supposed to be const.

You're right in that const void * is not assignable to const char ** without a cast because it would not be const correct. I personally try to avoid casts as much as is meaningfully possible, and therefore would prefer your code over the one that uses casts.

However It would be somewhat more correct to use const char *const *sp1, because there is no reason why cmpstringp shouldn't actually work for const char ** too!

Reordered and aligned it becomes more clear what is converted and what is conserved from right to left:

   int cmpstringp(void const *vep1, const void *vep2) {
    
                char * const *sp1    = vep1, ...

The const is copied, and the "to void" is replaced by "to pointer (to char). It does not even have to be a pointer (e.g. a struct). If, then I doubt this const-ness can be spread onto all references, just because a "compare" function is not supposed to modify at all. It may modify all objects, but not the ones being sorted.

This is all very interesting but logically complicated. Like having a row of boxes with letters inside. First you sort the letters inside each box according to themselves (not touching the boxes), then you sort the boxes according to the letters (but not touching them, the letters).

Thank you for comments and answer.


6.5.16.1 Simple assignment probably handles this case.

3 bullets for pointer assignment seem to demand:

  • compatible types, or one is void
  • left side has all the qualifiers of the right side.
  • null pointer on the right is no problem
Related