Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"Incompatible pointer type" compiler warning for 4th argument of qsort

I'm trying to use the standard library's qsort to sort an array of wide characters:

wchar_t a = L'a';
wchar_t a1 = L'ä';
wchar_t b = L'z';
wchar_t chararray[] = {b, a, a1};  
length = wcslen(chararray);

qsort(chararray, length, sizeof(wchar_t), wcscoll);

Now I think the functions involved have these prototypes:

int wcscoll(const wchar_t *ws1, const wchar_t *ws2);
void qsort(void *base, size_t num, size_t size, int (*comp_func)(const void *, const void *))

The results are completely as expected, but why am I getting the compiler warning "passing argument 4 of ‘qsort’ from incompatible pointer type"? And how can I cast wcscoll to fit the prototype?

The warning goes away if I define and pass in a separate comparison function:

int widecharcomp(const void *arg1, const void *arg2)
{
    return wcscoll(arg1, arg2);
}

... but this one looks like it should have error handling for when the arguments are not of type wchar_t *.

like image 568
chryss Avatar asked Dec 09 '22 14:12

chryss


2 Answers

You've done pretty much the right way. The gcc documentation for strcoll and wcscoll gives an example similar to this as the correct way to use strcoll or wcscoll with qsort.

 /* This is the comparison function used with qsort. */

 int
 compare_elements (char **p1, char **p2)
 {
   return strcoll (*p1, *p2);
 }

 /* This is the entry point---the function to sort
    strings using the locale's collating sequence. */

 void
 sort_strings (char **array, int nstrings)
 {
   /* Sort temp_array by comparing the strings. */
   qsort (array, nstrings,
          sizeof (char *), compare_elements);
 }

This example actually does raise the warning that you want to get rid of, but again it can be gotten around by changing the char** to const void* in the arguments to compare_elements, and then explicitly casting to const char**.

You're right in observing that this is type-unsafe, but type safety is not exactly one of C's strong points. C doesn't have anything like generics or templates, so the only way that qsort can work on an arbitrary type is for its comparison function to accept void*s. It's up to the programmer to make sure that the comparison function is not used in a context where it may be passed arguments that are not the expected type.

That said, there is an error in your code. What the comparison function receives is not the elements to be compared, but rather pointers to the elements to be compared. So if the elements are strings, that means pointer-to-pointer. So when you write

return wcscoll(arg1, arg2);

You are actually passing wscoll a wchar_t** when it expects a wchar_t*. The correct way to do this, while suppressing the warning, would be:

int widecharcomp(const void *arg1, const void *arg2)
{
    return wcscoll(*(const w_char_t**)arg1, *(const w_char_t**)arg2);
}

as ugly as that is.

Edit:

Just took another look at the top bit of your code. Your error is really twofold here. You're trying to use wcscoll to sort characters. It's a function meant to sort strings (which in C are pointers to nul-terminated sequences of characters). The above was written assuming you were trying to sort strings. If you want to sort characters, then wcscoll is not the appropriate function to use, but everything above regarding qsort still applies.

like image 158
Tyler McHenry Avatar answered Dec 12 '22 04:12

Tyler McHenry


There are two problems: you've mixed up wchar_t and wchar_t*, and you've tried to pass off a wchar_t* as a void*.

First, you've told qsort to sort an array of wchar_t. But wcscoll doesn't compare wchar_t, it compares wide character strings which have the type wchar_t*. The fact that your comparison appears to have worked is due to your test data which just happens to work well under both interpretations.

If you wanted to sort characters, you need to call an appropriate function (I don't know the wide character API well enough to tell you which one). If you wanted to sort strings, you need to allocate an array of strings (of type wchar_t *).

Furthermore, even if you had an array of wchar_t*, you could not portably pass wcscoll as an argument to qsort. The issue is that there is no guarantee that wchar_t* and void* have the same representation. Some machines have word pointers that have a different representation from byte pointers; on such a machine, qsort would pass byte pointers to elements of the array to wcscoll, and this wouldn't work because wcscoll expects byte pointers. The solution is to write a trivial wrapper function that performs the conversion if necessary. A trivial wrapper is often necessary with qsort.

like image 42
Gilles 'SO- stop being evil' Avatar answered Dec 12 '22 04:12

Gilles 'SO- stop being evil'