4

Here is the compare function:

int compare(const void *a, const void *b) { char* *s = (char* *) a; char* *t = (char* *) b; return sort_order * strcmp(*s, *t); // sort_order is -1 or 1 } 

Now my question is what is the reasoning behind casting to a double pointer of a particular type? Or rather, why is the double pointer cast needed and how is it used internally?

Other variables used: char **wordlist; int nbr_words; (array elements are) char *word;

Ex qsort call: qsort(wordlist, nbr_words, sizeof(char *), compare);

11
  • Not an answer, but your code contains integer overflow possibility if sort_order == -1 and strcmp returns INT_MIN. Commented Oct 22, 2013 at 20:29
  • In what case would strcmp return INT_MIN (which is also a macro for what value?)? Commented Oct 22, 2013 at 20:32
  • Your compare usage looks wrong, it should be a cast to (single)pointers to char and then strcmp them. Commented Oct 22, 2013 at 20:32
  • Well I don't know if it's wrong, because it's working. I just wanted to know the reasoning behind the cast to the double pointer :) Commented Oct 22, 2013 at 20:33
  • @ChrisCirefice My point was, that the cast is not logical. Commented Oct 22, 2013 at 20:34

1 Answer 1

6

It would help if you showed the definition of wordlist, but most likely it's defined as a char **. The compare() function receives a pointer to each element of your list. If each element of your list is of type char *, then compare() is going to receive two pointers to char *, or two char ** in other words.

The conversion to char ** (note that an actual cast would be superfluous, in this particular case, if you weren't going from a const void pointer, to a non-const char **) itself is necessary because qsort() has to work on any kind of type, so the arguments get converted to void * before they are passed. You can't deference a void * so you have to convert them back to their original types before doing anything with them.

For instance:

#include <stdio.h> int compare_int(void * a, void * b) { int * pa = a; int * pb = b; if ( *pa < *pb ) { return -1; } else if ( *pa > *pb ) { return 1; } else { return 0; } } int compare_double(void * a, void * b) { double * pa = a; double * pb = b; if ( *pa < *pb ) { return -1; } else if ( *pa > *pb ) { return 1; } else { return 0; } } int compare_any(void * a, void * b, int (*cfunc)(void *, void *)) { return cfunc(a, b); } int main(void) { int a = 1, b = 2; if ( compare_any(&a, &b, compare_int) ) { puts("a and b are not equal"); } else { puts("a and b are equal"); } double c = 3.0, d = 3.0; if ( compare_any(&c, &d, compare_double) ) { puts("c and d are not equal"); } else { puts("c and d are equal"); } return 0; } 

Outputs:

paul@local:~/src/c/scratch$ ./comp a and b are not equal c and d are equal paul@local:~/src/c/scratch$ 

The compare_any() function will compare any type which is supported, in this case, int and double, because we can pass a function pointer to it. However, the signature of the passed function must be the same, so we can't declare compare_int() to take two int * arguments, and compare_double() to take two double *. We have to declare them both as taking two void * arguments, and when we do this, we have to convert those void * arguments to something useful within those functions before we can work with them.

What's happening in your case is exactly the same, but the data themselves are pointers, so we're passing pointers to pointers, and so we need to convert void * to, in your case, char **.

EDIT: To explain some confusion in the comments to the original question about how qsort() works, here's the qsort() signature:

void qsort(void *base, size_t nmemb, size_t size, int(*compar)(const void*, const void*)) 

base is a pointer to the first element of an array, nmemb is the number of members of that array, and size is the size of each element.

When qsort() calls compar on, say, the first and second elements of your array, it'll send the address of the first element (i.e. base itself) and the address of the element (i.e. base + size).

If base was originally declared as an array of int, then the compare function must interpret those pointers it receives as pointers to int, as int *. If base was originally declared as an array of strings, as a char **, then the compare function must interpret those pointers as pointers to char *, i.e. as char **.

In all cases, the compare function gets pointers to elements. If you have an int array, then you must interpret those pointers as int * in your compare function. If you have a char * array, then you must interpret them as char **, and so on.

In this case, you obviously could call strcmp() if you just passed plain char * arguments to the compare function. But, because qsort() is generic it can only pass pointers to the compare function, it can't actually pass the value of your elements - it's the use of void * which allows it to be generic, because any type of object pointer can be converted to void *, but there is no equivalent datatype to which any non-pointer value can be converted. For that reason, it has to work the same way with regular types like int and double, with pointers, and with structs, and the only way to get it to work correctly with all possible types is to have it deal with pointers to elements, not with the elements themselves, even when the elements are themselves also pointers. For this reason, it might seem like you're getting an unnecessary level of indirection, here, but it actually is necessary in order for qsort() to be able to function in the generic way that it does.

You can see this more clearly if I modify the code above so that compare_any() is more similar to qsort(), and takes not two pointers, but a single pointer to a two-element array of various types (slightly contrived example, but we're keeping it simple):

#include <stdio.h> #include <string.h> int compare_int(void * a, void * b) { int * pa = a; int * pb = b; if ( *pa < *pb ) { return -1; } else if ( *pa > *pb ) { return 1; } else { return 0; } } int compare_double(void * a, void * b) { double * pa = a; double * pb = b; if ( *pa < *pb ) { return -1; } else if ( *pa > *pb ) { return 1; } else { return 0; } } int compare_string(void * a, void * b) { char ** pa = a; char ** pb = b; return strcmp(*pa, *pb); } int compare_any(void * arr, size_t size, int (*cfunc)(void *, void *)) { char * first = arr; char * second = first + size; return cfunc(first, second); } int main(void) { int n[2] = {1, 2}; if ( compare_any(n, sizeof(*n), compare_int) ) { puts("a and b are not equal"); } else { puts("a and b are equal"); } double d[2] = {3.0, 3.0}; if ( compare_any(d, sizeof(*d), compare_double) ) { puts("c and d are not equal"); } else { puts("c and d are equal"); } char * s[] = {"abcd", "bcde"}; if ( compare_any(s, sizeof(*s), compare_string) ) { puts("'abcd' and 'bcde' are not equal"); } else { puts("'abcd' and 'bcde' are equal"); } return 0; } 

Outputs:

paul@local:~/src/c/scratch$ ./comp a and b are not equal c and d are equal 'abcd' and 'bcde' are not equal paul@local:~/src/c/scratch$ 

As you can see, there's no way compare_any() could accept both an array of int, and an array of char *, without the compare_string() function getting a pointer it needs to treat as a char **, because of the pointer arithmetic it performs on the array elements. Without that additional level of indirection, neither compare_int() nor compare_double() could function.

Sign up to request clarification or add additional context in comments.

9 Comments

It looks to me like the values passed are elements to the array (surely). These are passed as pointers into the compare function. Since qsort takes any type, as you said, they're void pointers. So you have to cast them back to their original type, which makes sense. I guess I'm just confused as to why they aren't just cast back to char *X & char *Y, and then passed to strcmp as strcmp(X, Y).
Well looking at your edit, that makes sense! Still new to pointers, and I guess it just seemed like an extra unecessary * cast, but considering that it's not just a piece of data and the data is in itself a pointer, the extra step is needed. Thanks for the explanation :)
@ChrisCirefice: Because if your elements were char *, and compare() takes a pointer to an element, then you have to pass it a char **. compare() has to be consistent. If you were working with straight doubles, you'd have to pass a double *, because you can't cast double to void *. For the same reason, although passing a char * would work in general, this function requires that you pass pointers to your elements, so you have to pass it a pointer to pointer to char.
@PaulGriffiths I must say I am pleasantly surprised; I was wrong, somehow I confused pointers to an array.
qsort has the last parameter a function pointer with const paramenters.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.