First, pointers to char
and to unsigned char
are pretty much
exempted from rules regarding string aliasing; you are allowed
to convert any type of pointer to a char*
or an unsigned
char*
, and look at the pointed to object as an array of char
or unsigned char
. Now, with regards to your code:
size_t Process(char *buf, char *end)
{
char *p = buf;
ProcessSome((unsigned char**)&p, (unsigned char*)end);
//GCC decided p could not be changed by ProcessSome and so always returned 0
return (size_t)(p - buf);
}
The issue here is that you're trying to look at a char*
as if
it were an unsigned char*
. That's not guaranteed. Given
that the cast is clearly visible, g++ is being a bit obtuse
about not turning the strict aliasing analysis off
automatically, but technically, it is covered by the standard.
In
size_t Process(char *buf, char *end)
{
unsigned char *buf2 = (unsigned char *)buf;
unsigned char *p = buf2;
unsigned char *end2 = (unsigned char*)end;
ProcessSome(&p, end2);
return (size_t)(p - buf2);
}
on the other hand, all of the conversions involve char*
and
unsigned char*
, both of which may alias anything, so the
compiler is required to make this work.
With regards to the rest, you don't say what the return type of
buffer->GetData()
is, so it's hard to say. But if it is
char*
, unsigned char*
or void*
, the code is fully legal
(except for a missing cast in the second use of
buffer->GetData()
). As long as all of the casts involve
a char*
, an unsigned char*
or a void*
(ignoring const
qualifiers), then the compiler is required to assume that there
is a possible aliasing: when the original pointer has one of
these types, it could have been created by means of a cast from
a pointer to the target type, and the language guarantees that
you can convert any pointer into one of these types, and back to
the original type, and recover the same value. (Of course, if
the char*
wasn't originally a uint16_t
, you may end up with
alignment problems, but the compiler generally can't know this.)
With regards to the last example, you don't indicate the type of
hash.data
, so it's hard to say; if it is char*
, void*
or
unsigned char*
, the language guarantees your code
(technically, provided that the char pointer was created by
converting a size_t*
; in practice, provided that the
pointer is sufficiently aligned and the pointed to bytes do not
form a trapping value for a size_t
).
In general: the only really guaranteed way of "type punning" is
by memcpy
. Otherwise, the pointer casts, such as you are
doing, are guaranteed as long as it is to or from a void*
,
char*
or unsigned char*
, at least as far as aliasing is
concerned. (From one of these could result in alignment
problems, or accessing a trapping value if you dereference it.)
Note that you may get additional guarantees from other standards. Posix requires something like:
void (*pf)();
*((void**)&pf) = ...
to work, for example. (Generally, casting and dereferencing immediately will work, even with g++, if you don't do anything else in the function where aliasing might be relevant.)
And all of the compilers I know will allow using a union
for
type punning, some of the time. (And at least some, including
g++, will fail with legal uses of union
in other cases.
Correctly handling a union
is tricky for the compiler writer
if the union
isn't visible.)