So, the types are different (and incompatible),
Definitely.
but the values are the same.
Well, yes; but also no. Because they have different types, it is
impossible to compare the values, at least not without help.
Consider a similar example, using "int" and "float" instead:
int i = 3;
float f = 3.14;
Are these equal? Let us find out:
% cat t.c
#include <stdio.h>
int equal(int a, int b) {
return a == b;
}
int main(void) {
int i = 3;
float f = 3.14;
if (equal(i, f))
printf("i and f are equal\n");
else
printf("i and f are not equal\n");
return 0;
}
% cc -o t -O -Wall -W -ansi -pedantic t.c
% ./t
i and f are equal
%
Well, there you go -- 3 is in fact the same value as 3.14.
Of course, this is complete nonsense; and if we change the equal()
function to take two "double" values, we find that 3 != 3.14.
So which is it? Is 3 equal to 3.14, or is 3 not equal to 3.14?
Ask anyone numerate and you will hear "they are not", but in C,
they are -- at least, sometimes. Before we can compare them,
we have to convert them to a common type, and the conversion
process can change the values. Converting a "float" 3.14 to an
"int" truncates it to 3, so 3 == 3; converting both to double
expands 3 to 3.0, and leaves 3.14 as something close to 3.14
(it becomes about 3.140000104904, on a typical machine today).
Note that this last item -- the fact that 3.14 is not exact, and
as a double, actually has some digits after four more zeros --
means that sometimes, even 3.14 is not equal to 3.14 (depending
on just what is behind those zeros):
% cat u.c
#include <stdio.h>
int equal(double a, double b) {
return a == b;
}
int main(void) {
float f = 3.14;
if (equal(f, 3.14))
printf("f is equal to 3.14\n");
else
printf("f is not equal to 3.14\n");
return 0;
}
% cc -o u -O -Wall -W -ansi -pedantic u.c
% ./u
f is not equal to 3.14
%
Clearly, we have to be careful with conversions -- they make
things that are obvious (like 3.14 == 3.14) turn out to be false,
sometimes.
The same holds for pointers. Given two differently-typed pointers,
we have to convert at least one of them, if not both, before we
can even compare them. If we have:
T1 *p1;
T2 *p2;
where T1 and T2 are different types, and we assign values to p1
and p2, and then convert them:
if (p1 == (T1 *)p2)
and this claims they are equal -- well, what if this is like the
int 3 and the float 3.14, that are equal when we convert them both
to int?
In some sense, this whole thing is not even an interesting question.
If the types differ, we should not be comparing the things in the
first place. But sometimes we want to do it anyway. In that case,
how do we convert them without screwing up the result?
The int-and-float case continues to be instructive. If we rewrite
equal() to take two "float"s, 3 != 3.14, and we do not have to
worry about 3.14 != 3.14. So this looks like a good idea. But
hang on: what happens if we compare (int)33554432 with (double)33554433.0?
I am not going to quote the C code here, but the trick is, I have
chosen an int that is too big for a "float" on my machine, so that
the "double" 33554433.0 becomes 33554432.0 after conversion to
float. This makes the numbers equal, when obviously they are not.
It turns out that "float" is *not* good enough as a common type,
for comparing int-and-double. The reason is that, while float
obviously preserves all float values, conversion to float irrecoverably
alters certain int values. Similarly, int is not good for comparing
either int-and-double or int-and-float, because conversion to int
alters many float or double values.
What we really need is a common type that *always* produces a
recoverable transformation. That is, given two values v1 and v2
of types T1 and T2 respectively, we need a type T3 where:
(T1)(T3)v1 == v1 /* i.e., (T3)v1 is recoverable */
and:
(T2)(T3)v2 == v2 /* (T3)v2 is also recoverable */
Given such a type, we can finally come up with a usable definition
of "is equal to": v1 is equal to v2 if (T3)v1 == (T3)v2. This
definition is (I claim) also consistent if, for every additional
type T4 for which the "recoverable transformation" property holds,
the "is equal to" relationship remains unchanged when using type
T4.
For data pointers in C, there is definitely such a type T3: the
"void *" type (which must use the same underlying representation
as "char *", which has consequences that are beyond the scope of
this newsgroup posting
) allows you to convert any valid value
from some other data-pointer type, to "void *", then back, and
always get a result that compares equal to the original pointer.
Is there a type T4 (other than "char *" with its same-representation
thing)? This is implementation-dependent. So we can define an
"is equal to" relationship using "void *", but we cannot say for
certain whether it is consistent.
Note that, as Dave Vandervies already said, this "is equal to"
definition does *not* mean "is compatible with": on some machines,
passing a "char *" where an "int *" is required (or vice versa)
will not work properly, even if the two pointer values are "equal"
under void-star conversions. In this particular case, I am not
even sure you can prove that &arr and &arr[0] are "void-star-equal".
(I think they *are* void-star-equal; I just have trouble proving
it!)