This is undefined behaviour, because it is dereferencing a
pointer to an object of different type than the pointer base
type [4.1.1].
With one exception: you can always access memory as an array of
character types, see §3.10/15. (And it would seem that you've
found a bug in the standard, since 4.1.1 doesn't mention this
exception.)
Also see the strict aliasing rule [3.10.15]. This specifies
ways objects can be accessed, and using lvalue of type const
char* is not among them (interestingly, using a lvalue of
type char is).
Exactly. What do you thing ostream::write does with the pointer
you pass it, if not dereference it (resulting in an lvalue of
type char).
Some compilers use this rule to optimize variable accesses,
in your case, the compiler might just omit the assignment to
test altogether, because this is never read. Try looking at
the assembly to see if this is the case, and turning strict
aliasing rule off if this is possible.
A compiler that does this if one of the types is a character
type is not conformant. There are also other tricky cases where
the compiler cannot (according to the standard) count on the
lack of aliasing---some compilers (e.g. g++) get some of the
border cases wrong, however.
Traditionally, the purpose of reinterpret_cast is precisely to
allow this sort of aliasing. The results (except in the case of
character types) are formally undefined behavior, since there's
really not anything the standard can say about them, but as a
quality of implementation issue, they should work, at least in
cases where the reinterpret_cast is visible to the compiler.
The canonical way of doing this is using an union like this:
union {
int i;
char ch [4];
};
Now this *is* undefined behavior. Some compilers (e.g. g++) do
guarantee it, though.
Note that in practice, many compilers (including g++) will fail
at times with code like the following:
union U
{
int i ;
double d ;
} ;
int
tricky( int const* pi, double* pd )
{
int r = *pi ;
*pd = 0.0 ;
return r ;
}
// ...
U u ;
u.i = 43 ;
std::cout << tricky( &u.i, &u.d ) ;
Note that this code *is* fully conformant, with well defined
behavior according to the standard. I'm not sure that this is
intentional, however, since it pretty much negates the effect of
allowing the compiler to assume a lack of aliasing if the
pointer types are different. I rather suspect that both the C
and the C++ standard need serious rework in this respect. As it
stands, however, the compiler can only assume no aliasing
between two pointers if 1) they have different types, 2) neither
of the types is a character type, and 3) accesses are
sufficiently "interleaved" (e.g. a read of *pi after the write
through *pd, above). Some compilers forget this last
requirement, however.