Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Casting function pointer arguments without a helper function

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)));
like image 801
user18989277 Avatar asked Oct 21 '25 17:10

user18989277


2 Answers

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.

like image 170
Lundin Avatar answered Oct 23 '25 06:10

Lundin


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;
}
like image 37
Adrian Mole Avatar answered Oct 23 '25 07:10

Adrian Mole



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!