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

Is there a way to pass, let's say strcmp to qsort without making a helper function?

I was trying to do:

qsort(..., (int (*) (const void*, const void*) (strcmp)));
2
  • 4
    One major problem about not using correctly written helper functions is that qsort passes pointers to elements using the pointer-to operator &. So if you have an array of pointers the qsort function will pass pointer to pointers. And if you have an array or arrays then qsort will pass pointers to the arrays which will have the wrong type. So the result really depends on the data you want sorted. Commented Apr 29, 2022 at 12:22
  • 3
    Also casting between different function pointer types is by no means guaranteed to work. It is undefined behavior. Commented Apr 29, 2022 at 12:49

4 Answers 4

3

int (*)(const void*, const void*) and int (*)(const char*, const char*) are not compatible function pointer types.

Casting between different, non-compatible function pointer types is explicitly undefined behavior, C17 6.3.2.3/8 emphasis mine:

A pointer to a function of one type may be converted to a pointer to a function of another type and back again; the result shall compare equal to the original pointer. If a converted pointer is used to call a function whose type is not compatible with the referenced type, the behavior is undefined.


So if you cast strcmp to something else, you are explicitly invoking undefined behavior. It will likely work in practice on any system where all pointer types are of equal size. But if you are going to rely on that, you might as well cook up something like this:

typedef union
{
  int (*strcmp) (const char*, const char*);
  int (*compare)(const void*, const void*);
} strcmp_t;

const strcmp_t hack = { strcmp };
...
qsort(str, x, y, hack.compare);

This is just as undefined behavior (and as likely to work in practice) but more readable.


You can never do qsort(str, x, y, strcmp) because again strcmp is not compatible with the function pointer type expected by qsort. Function parameter passing is done as per assignment, so the rules of simple assignment are the relevant part, from C17 6.5.11:

Constratints
...

  • the left operand has atomic, qualified, or unqualified pointer type, and (considering the type the left operand would have after lvalue conversion) both operands are pointers to qualified or unqualified versions of compatible types, and the type pointed to by the left has all the qualifiers of the type pointed to by the right;

Therefore qsort(str, x, y, strcmp) is always invalid C and this is not a quality of implementation issue. Rather, compilers letting this through without diagnostics are to be regarded as hopelessly broken.


And finally as noted in comments, strcmp only makes sense to use with bsearch/qsort in case you have a true 2D array of characters such as char str[x][y];. In my experience that's a rather rare use-case. When dealing with strings, you are far more likely to have char* str[x], in which case you must write a wrapper around strcmp anyway.

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

Comments

2

Your attempt at the cast simply has a misplaced right (closing) parenthesis. The one at the end should be after the type of the cast. So, you can change:

(int (*) (const void*, const void*) (strcmp))
//                                          ^ wrong

to

(int (*) (const void*, const void*)) (strcmp)
//                                 ^ right

Alternatively, although hiding pointer types in typedef aliases is severely frowned-upon, function pointer types are an exception to that guideline. So, it is easier/clearer to define the required type for the qsort comparator first:

typedef int (*QfnCast) (const void*, const void*);

Then, you can cast to that type:

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

typedef int (*QfnCast) (const void*, const void*);

int main(void)
{
    char list[5][8] = {
        "Fred",
        "Bob",
        "Anna",
        "Gareth",
        "Joe"
    };

    qsort(list, 5, 8, (QfnCast)(strcmp));
    for (int i = 0; i < 5; ++i) printf("%s\n", list[i]);
    return 0;
}

7 Comments

"function pointer types are an exception to that guideline" Not really. Instead do typedef int QfnCast (const void*, const void*); ... (QfnCast*). No hidden pointers behind typedef and as a bonus your syntax is now consistent with object pointers.
"you don't actually need that cast" You absolutely do, since conversions between incompatible function pointers is a constraint violation of simple assignment. It can never happen implicitly. As for the rationale behind the warning, it is there to tell you that your code is invoking undefined behavior.
@Lundin Your second point is indisputable, and I have removed that part of my answer. Your first point is a bit more 'style'-oriented. I appreciate your point of view, though.
Could you also explain why you need the parenthesis around the asterisk (*)
@user18989277 That's how C syntax for function pointers works: int (*) (const void*, const void*) specifies a pointer to a function returning an int. Without those brackets around the *, then int * (const void*, const void*) specifies a function returning a pointer to an int.
|
2

There are two problems with what you're trying to do.

First, strcmp has type int (*)(const char *, const char *). This type is incompatible with the type int (*)(const void*, const void*) expected by the function because the parameter types are not compatible. This will result in qsort calling strcmp via an incompatible pointer type, and doing so triggers undefined behavior.

This might work if char * and void * have the same representation, but there's no guarantee this will be the case.

The second problem is that even if the call "works", what's ultimately being passed to strcmp isn't actually a char * but a char **. This means that strcmp will be attempting to read a char * value as if it were a sequence of char values.

So you have to use a helper function to get the results you want:

int compare(const void *a, const void *b)
{
    const char **s1 = a;
    const char **s2 = b;
    return strcmp(*s1, *s2);
}

1 Comment

Actually, there is a guarantee that char * and void * have the same representation. C17 6.2.5/28 says exactly that: "A pointer to void shall have the same representation and alignment requirements as a pointer to a character type." But that does not make the two function pointer types compatible, and does not change the fact that calling a function via a function pointer of incompatible type produces UB.
1

as @some programmer dude has already stated, it depends on what you're sorting. If it's an array of strings, you can use strcmp without a helper function and do a cast to avoid ugly warnings:

char s_array[100][100] = { "z", "a", ... };

qsort( s_array, 100, 100, (int (*)(const void *, const void *))strcmp );

If it's an array of pointers you need a helper function because it gets passed pointers to pointers:

char *p_array[100] = { "z", "a", ... };

int cmp( const void *p1, const void *p2 )
{
    return strcmp( *(const char **)p1, *(const char **)p2 );
}

qsort( p_array, 100, sizeof *p_array, cmp );

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.