"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';
wcha开发者_如何转开发r_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 *
.
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.
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
.
You've coded up your solution already (however, see other answers and edits at the end of this one about with the choice of the comparison function you're using and the data being passed to qsort()
).
You could drop the wrapper function by casting the function pointer you pass to qsort()
to the appropriate type, but I think using a wrapper is a better solution from a maintainability perspective. If you really want to avoid a wrapper function (maybe you're running into a measurable running into perf issue), you can cast like so:
qsort(chararray, length, sizeof(wchar_t), (int(*)(const void*,const void*))wcscoll);
Or make it arguably more readable using a typedef for the compare function type:
typedef
int (*comp_func_t)(const void *, const void *);
/* ... */
qsort(chararray, length, sizeof(wchar_t), (comp_func_t) wcscoll);
Unfortunately, the straight C qsort()
can't be typesafe, so it can't have have "error handling for when the arguments are not of type wchar_t". You, the programmer, are responsible for ensuring that you're passing the correct data, sizes and comparison function to qsort()
.
Edit:
To address some of the problems mentioned in other answers about the types being passed ot the compare function, here's a routine that can be used to sort wchar_t using the current locale's collating sequence. The library might have something better, but I'm not aware of it at the moment:
int wchar_t_coll( const void* p1, const void* p2)
{
wchar_t s1[2] = {0};
wchar_t s2[2] = {0};
s1[0] = * (wchar_t*)p1;
s2[0] = * (wchar_t*)p2;
return wcscoll( s1, s2);
}
Also note, that the chararray
you're passing to wcslen()
isn't properly terminated - you'll need a 0
at the end of the initializer:
wchar_t chararray[] = {b, a, a1, 0};
You can't cast a function pointer to a different type, your current solution is as good it gets
精彩评论