void * and explicit cast

C

CBFalconer

Keith said:
CBFalconer said:
blargg 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?

The C standard.

Chapter and verse? (Actually, I think somebody else may already
have answered that, but I wasn't paying much attention.)

I just know that I have read it in the standard. I'm not about to
undertake a search. Dan Pop would have spit it back. Where is he?
 
T

Tim Rentsch

Keith Thompson said:
CBFalconer said:
blargg 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?

The C standard.

Chapter and verse? (Actually, I think somebody else may already have
answered that, but I wasn't paying much attention.)

I gave these in earlier postings, but I can give them again here.


jameskuyper said:
pete said:
[snip snip]

ISO/IEC 9899:1999 (E)
6.3.2.3 Pointers
7
When a pointer to an object is
converted to a pointer to a character type,
the result points to the lowest addressed byte of the object.

pv is a pointer to void. "void" is an incomplete type, not an object
type, so pv does not point to an object. It points to a location in
memory, but the standard doesn't specify which location it points at
(we all "know" where it points, but the standard fails to say so). If
the standard guaranteed some kind of chain rule for pointer
conversions: (A*)(B*)p == (A*)p, that would be the missing link in the
argument; but it doesn't. In general, two-step pointer conversions
have a standard-defined result only when they are conversion pairs
that bring a pointer value back to its original type. The exceptions
are conversion pairs where the intermediate type is a pointer to a
character type, or where one of the conversions converts a pointer to
a struct type into a pointer to the type of it's first member, or vice
versa.

This reasoning rests on the idea that a pointer to void (since void is
an incomplete type) is not a pointer to an object. That notion is not
consistent with other statements made in the Standard.

For example, (struct foo *) and (int (*)[]) are both pointers to
incomplete types (assuming that 'struct foo' hasn't had its contents
defined). But pointers of these types still point to objects,
wouldn't you agree?

Nor is (void *) special in this regard. 7.20.3, talking about calloc,
malloc, and realloc (all of which return type (void*)), says:

Each such allocation shall yield a pointer to an object
disjoint from any other object.

Furthermore, the term 'incomplete type' itself explicitly includes
object-ness, using the definition in 6.2.5 p 1:

Types are partitioned into object types (types that fully describe
objects), function types (types that describe functions), and
incomplete types (types that describe objects but lack information
needed to determine their sizes).

Incomplete types describe objects.

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.


And then...

Tim Rentsch wrote:
[...]
[as excerpted above]

What's to keep it from pointing to the last byte of the object?

int i;
[snip]

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. 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?
 
T

Tim Rentsch

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.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,995
Messages
2,570,236
Members
46,825
Latest member
VernonQuy6

Latest Threads

Top