Tim said:
Tim Rentsch wrote:
[...]
A pointer to an incomplete type is still a pointer to an object
(assuming the pointer value isn't null); and in particular a
non-null pointer to void is still a pointer to an object.
Converting a (void*) to a (char*) gives the lowest addressed byte
of the object the (void*) points to. Since (void*) carries no
information about the size of the object it points to, the only
place it can point is the lowest addressed byte of the object.
What's to keep it from pointing to the last byte of the object?
int i;
void* vp = &i; // vp = (char*) &i + (sizeof(int) - 1)
int* ip = vp; // ip = (int*) ((uintptr_t) vp - (sizeof(int ) - 1))
char* cp = vp; // cp = (char*) ((uintptr_t) vp - (sizeof(char) - 1))
assert( ip == &i ); // OK
assert( cp == (char*) &i ); // would fail
Is there anything other than 6.3.2.3 that specifies what a conversion
to/from void* involves?
Consider the following expression:
&i == (void*) &i
I expect you'll agree that this expression is well-formed, and that it
must yield the value 1.
Sure. I'm not sure which conversion occurs to bring the two sides to a
common type, so I'll cover both cases:
(void*) &i == (void*) &i // obviously they will match
&i == (int*) (void*) &i // int*->void*->int* must yield same pointer
The first case is uninteresting, and the second just re-states what the
code above demonstrates, that converting to a void* and back yields the
same value. So I fail to see how your example shows that it must point at
the first byte.
According to 6.5.9 p 6, this means the two
pointers are pointers to the same object, or perhaps an object and
a subobject at its beginning. In either case, the two objects pointed
to have the same lowest addressed byte; ergo we can conclude that
(char*) &i == (char*) (void*) &i
Right?
The snag seems to be that the comparison can only take place after the
pointers are brought to a common type, but bringing them to that type
already ensures they will be equal. Thus this char* version would really
be one of the following, which will obviously match regardless of how
conversion to void* is done:
(char*) (void*) &i == (char*) (void*) &i
(char*) &i == (char*) (int*) (void*) &i
Every so often I see a comp.{lang,std}.c posting that is so
surprising I have to mull it over for a time to make sure I
understand the different viewpoints. The posting above is one of
those.
Here blargg and I reach different conclusions because of an
ambiguity about which pointers are being compared. (More
accurately, I reached a conclusion, and afterwards blargg used
his reading of the ambiguous section to argue that there's a flaw
in my reasoning.)
The paragraph in question, 6.5.9 p 6, reads as follows:
Two pointers compare equal if and only if both are null pointers,
both are pointers to the same object (including a pointer to an
object and a subobject at its beginning) or function, both are
pointers to one past the last element of the same array object,
or one is a pointer to one past the end of one array object and
the other is a pointer to the start of a different array object
that happens to immediately follow the first array object in the
address space.
My reading is, for a comparison like &i == (void*) &i, the pointers
being compared are &i and (void*) &i , whereas blargg's reading
(not meaning to put words in his mouth, but extrapolating based on
how pointer comparisons are done, per 6.5.9p5) is that the pointers
being compared for &i == (void*) &i are (void*)&i and (void*)&i.
I believe either reading is plausible, even taking into account
everything else the Standard says about types and conversions.
I don't want to argue that either interpretation has a stronger
argument supporting it, although it's very likely one or the other
does.
Despite this ambiguity, I believe my original conclusion is still
valid, because of how the requirements for pointer comparison are
written. 6.5.9p6 says pointers are equal if both are pointers to
the same object, or "a pointer to an object and a subobject at
its beginning". Why is the second part included? For it to be
relevant there must be cases where pointers to two different
objects can be compared and where one pointed-to object is a
subobject-at-the-beginning of the other. How can this happen?
Clearly it can't happen if both pointers reference complete
types, because either the sizes of those types are the same
(which eliminates the subobject possibility) or the pointers
can't be compared because of type incompatibility. At least one
of the pointers must be a pointer to an incomplete type, as for
example (void *).
All this is consistent with the idea that converting a pointer to
(void*) yields a pointer to the same object as that of the
original pointer. Otherwise, there doesn't seem to be any point
to mentioning the object/subobject case. (There is one other
case where a pointer to an object type can be compared to a
pointer to an incomplete type, but that case isn't as compelling
as cases where (void*) is involved.[1])
In addition to 6.5.9p6, there is the discussion of relational
operators in 6.5.8, specifically 6.5.8 p 5, which says:
When two pointers are compared, the result depends on the
relative locations in the address space of the objects pointed
to. If two pointers to object or incomplete types both point to
the same object, or both point one past the last element of the
same array object, they compare equal. If the objects pointed to
are members of the same aggregate object, pointers to structure
members declared later compare greater than pointers to members
declared earlier in the structure, and pointers to array elements
with larger subscript values compare greater than pointers to
elements of the same array with lower subscript values. All
pointers to members of the same union object compare equal. If
the expression P points to an element of an array object and the
expression Q points to the last element of the same array object,
the pointer expression Q+1 compares greater than P. In all other
cases, the behavior is undefined.
Note the rules about structure members and about union members.
These rules make sense only if converting a pointer to (void*)
points to the same object as the original pointer; otherwise
pointers to members having different types can't be compared,
whereas the text indicates that they can. Of course, it's
possible to read 6.5.8p5 as applying only to members whose
pointers can be compared directly, without conversion. But this
reading seems pretty strained, and at odds with the "All"
in the sentence about unions. To say that somewhat differently,
if converting to (void*) /doesn't/ point at the same object
as the original pointer, then cases like
union { int x; short y; } u;
return (void*)&u.x <= (void*)&u.y;
produce undefined behavior (because of the last sentence in the
text of 6.5.8p5). In fact, if converting to (void*) doesn't have
to point at the same object as the original pointer, then any
relational comparison between (void*) pointers would always
produce undefined behavior. (Remember, if (void*) conversion
doesn't have to point at the same object as the original, then
there is no guarantee that (void*)p == (void*)p -- all that's
required is that (T*)(void*)p == (T*)(void*)p, if p is of type
T*.) It seems unlikely that Standard would allow relational
comparison of (void*) pointers if doing so always produced
undefined behavior; the simpler reading -- that converting to
(void*) points at the same object as the original pointer -- is
more consistent.
Moveover, I think it's worth comparing the language in 6.3.2.3
(talking about pointer conversion) with the language used in the
rest of section 6.3 (talking about conversions more generally).
In all other cases, what happens to the values being converted is
described in their respective paragraphs, and in particular when
the new value is the same as the old value. The difference is,
pointers don't have identifiable "values" as such. To identify
the cases where the new "value" is the same as the old "value",
the Standard expresses this condition by giving an equality
relationship on converted pointer values. I agree that this way
of expressing "same valueness" is perhaps too oblique, but I
don't see any other way of reading it that's really consistent
with 6.5.8p5 and 6.5.9p6.
Summing up, here are the choices as best I understand them:
(1) Converting a pointer to (void*) yields a pointer to the
same object as the original pointer, in which case the
relationship (char*) &i == (char*) (void*) &i is guaranteed;
or,
(2) A pointer converted to (void*) may point to any object at
all, subject only to the constraint that upon being converted
back to the type of the original pointer that it equal the
original pointer value; under this case expressions like
(void*) &a[0] < (void*) &a[1]
produce undefined behavior.
It's hard to imagine anyone seriously arguing that interpretation (2)
is reasonable, except perhaps in comp.std.c as part of an argument
that the language describing pointer conversions be improved. (And
since I'm having such trouble imagining it, no doubt another reader of
comp.lang.c will try to help me out by offering up just such an
argument.
====================
[1] I don't say what the other case is so as not to deprive
interested readers of the pleasure of finding it themselves.