5

The last parameter of this line in main() makes me lost

// declaration
void qsort(char *linep[], int left, int right, int (*compare)(void *, void*);

// use
main(){
    qsort((void**) lineptr, 0, nlines-1, (int (*)(void*,void*))(numeric ?
    numcmp : strcmp));
}

I understand the ternary operator but let's say numeric == 0 then what does this mean?

(int (*)(void *, void*))strcmp;

Do datatypes of function parameters mismatch?

int strcmp(const char*, const char*);
void qsort( , , , int(*)(void*)(void*);

Can I typecast a function pointer?

Lundin
  • 195,001
  • 40
  • 254
  • 396
Pruzo
  • 87
  • 1
  • 8
  • Beware of K&R, it is a dangerous book which teaches lots of incorrect or poor programming practice. This example 5.11 is one such example, as the code relies on undefined behavior. In addition, as we can see, the K&R code is also an unreadable mess. – Lundin Feb 26 '16 at 14:19
  • I always get flamed for posting a K&R like code somewhere, thank you for this! – Pruzo Feb 26 '16 at 14:26
  • Your question is perfectly fine, it is the K&R book that is the problem, not your question. It is very reasonable to ask about these things, because it really does seem logical to assume that the creator of the language would know the language better than anyone else. Alas, this was only true until the year 1989. The attempt to upgrade the K&R book to the supposedly ISO C compliant 2nd edition was sloppy; lots of pre-standard code remains. – Lundin Feb 26 '16 at 14:38
  • @Lundin Actually I don't believe this is wrong. After all, ` char*` and `void*` are compatible in function declarations. – fuz Feb 26 '16 at 14:40
  • @FUZxxl The problem is that nothing in the standard guarantees that `int(*)(const void*, const void*)` and `int(*)(char*, char*)` are compatible. – Lundin Feb 26 '16 at 14:42
  • `gcc -std=c11 -pedantic-errors` agrees with me. "error: assignment from incompatible pointer type". – Lundin Feb 26 '16 at 14:46
  • 1
    @Lundin Which seems to indicate that you are pedantic ;-) – Peter - Reinstate Monica Feb 26 '16 at 14:52
  • 2
    And in my opinion K&R is one of the finest technical books around. In fact, its usability and readability and pragmatic attitude while keeping the fun are one of the reasons C is so successful. Compare that to the report on Algol68... The C standard has been refined since because in some fringe cases or exotic architectures certain constructs could conceivably crash, but this is certainly not one of them. – Peter - Reinstate Monica Feb 26 '16 at 14:59
  • So you get flamed for posting K&R code for the same reasons there are so many lawyers in the U.S.: focus on formalia. – Peter - Reinstate Monica Feb 26 '16 at 15:01
  • @PeterA.Schneider Well, I didn't name the gcc options, nor did I make the moronic decision to set it as a non-standard compiler by default. To use gcc as a C compiler, you must always pass it the mentioned options. Otherwise it will forever remain a gnu, instead of evolving into a beautiful conforming C implementation. You wouldn't want to have a sad gnu inside your computer, now would you? – Lundin Feb 26 '16 at 15:02
  • @Lundin If the gnu gets me from A to B I'll prefer a Gnu any time. – Peter - Reinstate Monica Jul 29 '20 at 09:57

2 Answers2

5

In your code, using the cast

  (int (*)(void *, void*))strcmp;

means, strcmp() is a function pointer, which takes two void * arguments and returns an int.

Usually, for function pointers, casting is a very bad idea, as quoting from C11, chapter §6.3.2.3

[...] If a converted pointer is used to call a function whose type is not compatible with the referenced type, the behavior is undefined.

but, in your case, for the argument types, char * and void * alias each other, so the typecasted type is compatible with the actual effective type(s), so (at a later point) the function call is defined.

Sourav Ghosh
  • 133,132
  • 16
  • 183
  • 261
  • so what i did was that i typecasted int strcmp(char *, char *) to int strcmp(void *, void *) and passed it as argument? – Pruzo Feb 26 '16 at 14:00
  • In this case, casting is also unneccesary, isn't it? Simply calling `qsort` with the last parameter as `strcmp` should work due to an implicit cast from `const char *` to `void *`. – R_Kapp Feb 26 '16 at 14:05
  • @R_Kapp correct, but all implicit casts may not be _valid_, right? – Sourav Ghosh Feb 26 '16 at 14:07
  • I'm quite sure that the function pointer conversion is not fine, the question isn't if `void*` and `char*` alias each other, but if `int(*)(const void*, const void*)` and `int(*)(char*, char*)` do. They don't, so (yet again) K&R invokes undefined behavior. – Lundin Feb 26 '16 at 14:22
  • @Lundin to get rid of undefined behavior we typecast it with int (*)(void *, void *) right? Now I'm lost once again. – Pruzo Feb 26 '16 at 14:28
  • 1
    @Pruzo No, function pointer casts are undefined behavior, as per the text cited by Sourav. The only way to get rid of it is to either change the function definitions to the expected format by `qsort`, or to wrap those functions inside other functions with the correct format. – Lundin Feb 26 '16 at 14:32
  • Now I got it! Thank you. – Pruzo Feb 26 '16 at 14:37
  • 1
    @Pruzo For example: `int wrap_strcmp (const void* o1, const void* o2) { return strcmp(o1, o2); }` – Lundin Feb 26 '16 at 14:39
  • @Lundin The cast is not the undefined behaviour, calling the casted function would be but I'm not sure if this is the case as `char*` and `void*` parameters are exchangable as far as I'm concerned. – fuz Feb 26 '16 at 14:41
  • @FUZxxl Indeed, and `qsort` will call the function. – Lundin Feb 26 '16 at 14:43
  • wouldn't you pass in `int wrap_strcmp (const void* o1, const void* o2) { return strcmp(o1, o2); }` a void pointer to a charp parameter? so shoulnd't you call `strcmp((char *)o1, (char *) o2)`? – Pruzo Feb 26 '16 at 14:52
  • @Pruzo Yes you would pass a `const void*` to a function accepting `const char*` and that is fine. Any pointer to object (to a variable) can get implicitly converted to/from `void*` type without casts, given that they have the same qualifier ("constness"). As a side note, C++ does not allow this - in C++ you must use an explicit cast. – Lundin Feb 26 '16 at 14:57
1

Yes, you can cast a function pointer to a pointer to a function with a different signature. Depending on your calling convention (who cleans the stack up? The caller or the callee?) calling that function will be bad if there is a different number of arguments or their sizes differ.

Neither is the case here: On your standard architecture (sun workstations, Linux PCs, raspberry PI) the argument pointers to different data types are represented identically so that no harm is expected. The function will read the 4 or 8 byte value from the stack and interpret the memory pointed to as data of the expected type (which it should have though, e.g. don't use a float compare function on strings; it may throw because arbitrary bit patterns can be NaNs etc.).

I wanted to alert you to the fact that today's standard lib's qsort has a different function signature (and semantic) than K&R's example. Today's qsort gets a pointer to the beginning of an element vector and calls the compare function with pointers to the elements in the array; in the case of an array of string pointers, the arguments are pointers to pointers which are not suitable for strcmp(). The arguments have to be dereferenced first. The linux man page for qsort has an example for a strcmp wrapper which does just that. (The man page web export appears somewhat garbled, but is still readable.)

Peter - Reinstate Monica
  • 15,048
  • 4
  • 37
  • 62