The C standard mandates that a “pointer to void shall have the same representation and alignment requirements as a pointer to a character type”. In consequence, the same holds for an array of void pointers vs. an array of char pointers. The code therefore seems valid to me, and furthermore at no point is a function called with an argument type different from its formal parameters.
In the GP example, a char** is passed where a char* is expected. That is clearly invalid.
I was more referring to how, in the K&R example, a function of type "int (*)(char *, char *)" is cast to "int (*)(void *, void *)", in contradiction to what they said here:
> Before someone mentions qsort(): the comparator function really is supposed to take a void*, and inside the function, you re-cast the void* argument to a pointer type of your desire. If you don't do it in that order, you're using it wrong.
In contrast, the K&R example is an example of explicitly undefined behavior in the C standard:
The behavior is undefined in the following circumstances:
- A pointer is used to call a function whose type is not compatible with the pointed-to type (6.3.2.3).
The second edition was released in 1988, and it was based on a draft of the first ANSI C standard, and even then, the line stating that this was undefined behavior was already present.
> A pointer to a function is converted to point to a function of a different type and used to call a function of a type not compatible with the original type (3.3.4).
Ah, I missed that. However, footnote 41 states “The same representation and alignment requirements are meant to imply interchangeability as
arguments to functions […]”, which could be taken as implying compatibility of function types, though the normative wording doesn’t directly support that.
In the GP example, a char** is passed where a char* is expected. That is clearly invalid.