This question is about the 'function pointer' version of qsort
from K&R (2e), section 5.11 (p118-121). There are a number of places where I don't understand why or how the casts work, and I suspect that the program is not entirely standards-conformant.
Here is the source code. Aside from removing comments and making minor changes to the formatting, I:
- renamed
qsort
toqsort_alt
(for "alternative qsort"), because the standard library already contains a function namedqsort
, - changed the function signature of
main
toint main(void)
, and - simplified and altered
main
to use input supplied via thelinearr
andwlinearr
variables instead ofstdin
.
#include <stdio.h>
#include <string.h>
#include <wchar.h>
char *linearr[] = {
"13", "5", "10", "9", "23", "2", "5",
"25", "3", "13", "1", "4", "14", "19",
};
wchar_t *wlinearr[] = {
L"13", L"5", L"10", L"9", L"23", L"2", L"5",
L"25", L"3", L"13", L"1", L"4", L"14", L"19",
L"42", L"32", L"25", L"5", L"3", L"34",
L"50", L"40", L"86", L"91", L"100", L"21",
L"29", L"28", L"23", L"93", L"24", L"89",
L"85", L"63", L"10", L"39", L"14", L"18",
L"78", L"73", L"52", L"57", L"26", L"38",
L"91", L"76", L"61", L"46",
};
void qsort_alt(void *v[], int left, int right,
int (*comp)(void *, void *));
int numcmp(char *, char *);
int main(void) {
int nlines = sizeof(linearr) / sizeof(linearr[0]);
int nwlines = sizeof(wlinearr) / sizeof(wlinearr[0]);
int i;
qsort_alt((void **)linearr, 0, nlines - 1,
(int (*)(void *, void *))numcmp);
for (i = 0; i < nlines; i++)
printf("%s%s", linearr[i], (i < nlines - 1) ? " " : "\n");
/* output: 1 2 3 4 5 5 9 10 13 13 14 19 23 25 */
qsort_alt((void **)linearr, 0, nlines - 1,
(int (*)(void *, void *))strcmp);
for (i = 0; i < nlines; i++)
printf("%s%s", linearr[i], (i < nlines - 1) ? " " : "\n");
/* output: 1 10 13 13 14 19 2 23 25 3 4 5 5 9 */
qsort_alt((void **)wlinearr, 0, nwlines - 1,
(int (*)(void *, void *))wcscmp);
for (i = 0; i < nwlines; i++)
wprintf(L"%ls%ls", wlinearr[i], (i < nwlines - 1) ? L" " : L"\n");
/* output: 1 10 10 100 13 13 14 14 18 19 2 21 23 23 24 25 25 26 28 29 3 3 32 34 38 39 4 40 42 46 5 5 5 50 52 57 61 63 73 76 78 85 86 89 9 91 91 93 */
return 0;
}
void qsort_alt(void *v[], int left, int right,
int (*comp)(void *, void *)) {
int i, last;
void swap(void *v[], int, int);
if (left >= right)
return;
swap(v, left, (left + right) / 2);
last = left;
for (i = left + 1; i <= right; i++)
if ((*comp)(v[i], v[left]) < 0)
swap(v, ++last, i);
swap(v, left, last);
qsort_alt(v, left, last - 1, comp);
qsort_alt(v, last + 1, right, comp);
}
#include <stdlib.h>
int numcmp(char *s1, char *s2) {
double v1, v2;
v1 = atof(s1);
v2 = atof(s2);
if (v1 < v2)
return -1;
else if (v1 > v2)
return 1;
else
return 0;
}
void swap(void *v[], int i, int j) {
void *temp;
temp = v[i];
v[i] = v[j];
v[j] = temp;
}
For me, this compiles fine (without warnings) with GCC and compiler flags -std=iso9899:199409 -pedantic -Wall -Wextra
and produces the output shown within the comments above. (The choice of iso9899:199409
reflects the fact that <wchar.h>
was introduced with C95, but actually this compiles and runs equally well with c90
.)
My call to qsort_alt
is substantially the same as in K&R (2e). For reference, in the original it was
qsort((void **) lineptr, 0, nlines-1,
(int (*)(void*,void*))(numeric ? numcmp : strcmp));
with int nlines
indicating the number of lines stored in char *lineptr[MAXLINES]
and numeric
being a boolean flag (set via the command line) that decides which of the two functions numcmp
/strcmp
is used. K&R's main
reads input from stdin
into char *lineptr[MAXLINES]
; I replaced it by char *linearr[]
. The input wchar_t *wlinearr[]
is my own addition. Relevant here is what K&R write (p119-120):
We have written
qsort
[in my code:qsort_alt
] so it can process any data type, not just character strings. [...] The elaborate cast of the function argument casts the arguments of the comparison function. These will generally have no effect on actual representation, but assure the compiler that all is well.
Let's see whether all is good, ie: whether the program is portable and whether it would work for other types such as wchar_t
, as implied by K&R:
1. Why are the arguments to qsort_alt
[orig: qsort
] cast explicitly?
The C17 standard draft says (6.5.2.2 ¶7):
If the expression that denotes the called function has a type that does include a prototype [ie: it doesn't use old-style syntax], the arguments are implicitly converted, as if by assignment, to the types of the corresponding parameters, taking the type of each parameter to be the unqualified version of its declared type.
That is, it seems that explicit casts to match the types of the function parameters of qsort_alt
aren't necessary, though they may be stylistically justified.
However, is the following wording (6.5.4 ¶3) relevant?
Conversions that involve pointers, other than where permitted by the constraints of 6.5.16.1 ["Simple assignment"], shall be specified by means of an explicit cast.
2. Is the cast (void **)linearr
[orig: (void **) lineptr
] legal? What about my (void **)wlinearr
?
6.3.2.3 ¶7:
A pointer to an object type may be converted to a pointer to a different object type. If the resulting pointer is not correctly aligned[] for the referenced type, the behavior is undefined.
Pointer types are object types, hence char *
and void *
are object types, and therefore pointers to these types can legally be converted. However, the second sentence is relevant as well. While we know that char *
and void *
are identically aligned (6.2.5 ¶28), this wouldn't necessarily be true for wchar_t *
and void *
: it is easy to imagine a situation where the former has half the bit width of the latter. (Recall that K&R claim that their qsort
"can process any data type".) That is, even if the cast is fine, my cast (void **)wlinearr
might not be.
There is other language in the standard (6.7.6.1 ¶2) where I am not sure whether it has any bearing on these issues:
For two pointer types to be compatible, both shall be identically qualified and both shall be pointers to compatible types.
linearr
is of type char * []
and decays to type char **
just before the cast to type void **
. For char **
and void **
to be compatible, char *
and void *
would need to be compatible, but it seems that they technically aren't compatible.
3. Are the casts of numcmp
/strcmp
from types int (*)(char *, char *)
/int (*)(const char *, const char *)
to type int (*)(void *, void *)
legal? What about my analogous cast of wcscmp
from type int (*)(const wchar_t *, const wchar_t *)
to type int (*)(void *, void *)
? (Note that the difference in const
ness isn't an issue.)
As the document "Errata for The C Programming Language, Second Edition" by Lucent Technologies (2002/2006) states (code formatting added in some places):
[T]he comparison-routine argument is not treated well. The call shown on p 119, with an argument
(int (*)(void*,void*))(numeric? numcmp : strcmp)
is not only complicated, but only barely passes muster. Bothnumcmp
andstrcmp
takechar *
arguments, but this expression casts pointers to these functions to a function pointer that takesvoid *
arguments. The standard does say thatvoid *
andchar *
have the same representation, so the example will almost certainly work in practice, and is at least defensible under the standard.
According to this answer, the casting of function pointers is only safe (as in: doesn't lead to undefined behavior) if the respective types are "compatible", but char *
and void *
are technically not compatible (see #2 above).
But perhaps this is really just to appease the compiler (as K&R assure us), so let's have a look at what happens in qsort_alt
:
4. Is the call to comp
unproblematic?
qsort_alt
computes v[i]
and v[left]
, which involves pointer arithmetic on an underlying void *
type. (Note that v
is of type void **
. While pointer arithmetic is illegal on void *
(with an underlying void
), it is not illegal on void **
(with an underlying void *
).) Because pointers to void
and to character types "have the same representation and alignment requirements" (6.2.5 ¶28), the index computations seem fine for K&R's original code, but do they also work portably for wchar_t
? It seems that a type-generic version of the program would need to supply the size of the underlying type (such as wchar_t *
) to qsort_alt
.
Furthermore, I am not sure whether it is really true that "[t]he elaborate cast of the function argument [within main
] casts the arguments of the comparison function". It seems to me that if any arguments of the comparison function are cast, that would happen within qsort_alt
when it calls comp
, but not within the call to qsort_alt
within main
. From the perspective of qsort_alt
, comp
is a function accepting void *
arguments, and any v[index]
is of type void *
, and therefore there don't seem to be any implicit argument conversions going on. In this regard, I am assuming that implicit argument conversions for function parameters would be performed by the caller, not the callee, but do correct me if I'm wrong. (Caller and callee may, like here, have differing beliefs about the parameters' types.) Despite the absence of a conversion, comp
will internally assume that its arguments are char *
(for K&R's original) or wchar_t *
(for my addition). Things should go well as long as the pointer arithmetic to get to the arguments v[i]
and v[left]
is proper.
Incidentally, I see calls within qsort_alt
to swap
as unproblematic, as swap
operates on a void **
argument and only reassigns pointers.