christer-sandberg wrote:
> Thanks for your answer.
> Eric Sosman wrote:
>
>> The argument to foo() is a `void*'. The cast specifies
>>an `int*' argument. `void*' and `int*' are incompatible,
>
> If I don't remember wrong, according to ANSI-C from 99 a void pointer
> can be assigned the value of a pointer to an object and they should
> compare equal. In what sense do you mean they should be incompatible?
The Standardese answer is "In the sense of 6.2.7, where
type compatibility is defined."
A (possibly) more helpful answer is by example. You can
convert any `int*' to a `void*' and back again, but it need
not be true that you can do the reverse: there may be `void*'
values that cannot be converted to `int*' successfully. On
many or even most present-day systems, such conversions can
produce values that are not usable as `int*'. So it's clear
that `int*' and `void*' are not the same type, even though
there are some pointer values that both can represent.
Or here's another: On every system I have ever encountered
(though I don't believe the Standard actually requires it),
every `char' value can be converted to `double' and back, and
the result compares equal to the original. Does this mean that
`char' and `double' are compatible types? Of course not.
> [...]
> I meant niether of them. Rather something like what I wrote (sorry for
> my bad English)
> a macro ensuring the correct type of parameters to the callback
> (otherwise the the parameters needs to be casted in the function
> definition in which case there is nothing ensuring that the type of the
> array is used) e.g.:
> #define QSORT(d, n, t, f) (void(*)(t*, size_t, size_t, int(*)(const t*,
> const t*))) \
> qsort(d, n, sizeof(t), f);
> I can be done some ather ways too, but as I see it, it is an advantage
> to be able to force the type of array, the size of the array elements
> and the types of the paramaters to the call.back be the same.
Ingenious -- but even worse than what I thought you were
attempting! I'd supposed you were trying to disguise `f' so
the compiler wouldn't complain when you passed it to qsort().
Instead, you're lying about the nature of qsort() itself:
- You're telling the compiler to pass the first argument
as a `t*', but qsort() expects to receive a `void*'.
If any conversion from `t*' to `void*' is needed, your
recipe will prevent it from happening; if `t*' and `void*'
are passed to functions differently (e.g. in different
registers), your recipe will put the first argument in
the wrong place.
- You're telling the compiler that the fourth argument is
a pointer to a function taking two `const t*' arguments,
and your recipe will cause the compiler to complain if
the actual `f' is different. But the real qsort() doesn't
want such an `f'; it wants an `f' that takes a pair of
`const void*'. If there's any difference between these
(e.g., if a function pointer isn't a mere address but
also carries something like a "dope vector"), you cannot
expect this to work.
- And despite all these shenanigans, qsort() is still going
to pass `void*' arguments -- not `t*' arguments -- when it
calls `f'. Since that's not what `f' expects to receive,
there's no telling what might happen.
I agree that gcc's insertion of abort() is a bit draconian,
but it's understandable: gcc is trying to catch "really bad
incompatibilities," but it can only discern "incompatibility;"
it seems to have no notion of "probably venial incompatibility."
And you have to admit that it's had at least one good effect:
it's brought you here to learn the error of your (ingenious
albeit misguided) ways!
Enforcement of matching types is a weak point of C: there
are a number of places where you must rely on vigilance and can
expect little if any help from the language or the compiler.
"Type-blind" functions like qsort() and bsearch() -- memcpy(),
for that matter -- use `void*' precisely because they want to
be able to operate on any kind of data object, but blindness
has its drawbacks. Type-checking is defeated (that's the
purpose, after all), but that also means you lose the benefits
of type-checking. Your effort to restore those benefits is
laudable, but I think it's doomed. You might go as far as
#define QSORT(a, n, f) \
qsort((a), (n), sizeof *(a), (f))
.... to ensure that the third argument is correct, but I can
think of no way to ensure that `f' is the right function to use
with the type of data that happens to be in `a'. (There's even
less hope that you could ensure that `f' imposes a total ordering
on the values of that type!) Some constraints of a function's
documentation are simply not expressible in the language; C
shares this problem with other languages, too.
"If you lie to the compiler [even with the best of
intentions], it will get its revenge."
--