Reputation: 35901
Consider this C code:
extern void CheckIfPtrInHeap( void* p );
void TakePtr( void** p, size_t n )
{
for( size_t i = 0 ; i < n ; ++i )
CheckIfPtrInHeap( p[ i ] );
}
typedef size_t val_t[ 6 ];
extern void* stack_top;
void Func()
{
val_t val;
TakePtr( (void**) &val, ( (size_t) stack_top - (size_t) &val ) / sizof( size_t ) );
}
It compiles fine with gcc 4.8, mingw 4.7, cl 18.00. Which might make sense because just from the looks of it I'd say there is no violation here: yes there are 2 pointers pointing to the same variable, but the second one's value is immediately cast to a size_t which requires a temporary, so the pointer itself cannot be used inside the function TakePtr.
Q1: Is my assumption above correct? And if it is, does this mean there really is no violation, or rather that the compiler figured out there is none while techically (i.e. following the standard by the letter) there is a violation (still two pointers to same variable being dereferenced)?
Q2: I stumbled upon this while using the mingw cross-compiler version 4.2.1 on linux, it raises warning: dereferencing type-punned pointer will break strict-aliasing rules
for the line with TakePtr. So the second question is actually like the first but in other words: is this compiler correct, or is it a bug, or would one rather call it 'compiler being too strict'?
Q3: Wanting compilation without warnings for all aforementioned compilers, what would the most correct fix in code be (maybe not just for this case, but for a more general one where there is a violation of the rule and void** pointers are used)? It seems there are multiple ways to silence the compiler:
union { val_t* r; void** ptr; } cast_ = { &val };
void** ptr = cast_.ptr;
or
void* p1 = (void*) &val;
void** ptr = &p1;
or even
void** ptr = (void**) (void*) &val;
or maybe (to tell the compiler 'hey I know what I'm doing', at least if I understood restrict
correctly)
void* restrict p1 = (void*) &val;
void** ptr = &p1;
or
val_t* p1 = &val;
void** p2 = NULL;
memcpy( p2, p1, sizeof( p1 ) );
Upvotes: 0
Views: 323
Reputation: 119877
Strict aliasing rules are simply the fact that it is illegal to access an object of one type through an lvalue of another type (with exceptions for things like void*
and char*
, and other obvious things). No more, no less.
Merely having two, or three, or 10000 pointers to the same object at the same time, whether they are all of the same type or not, has nothing to do with the strict aliasing rules. You need to dereference one of them for the violation to happen.
This is also totally unrelated to the restrict
keyword, which is not concerned with types at all, but simply informs the compiler that a pointer is unique.
TakePtr
is in violation regardless of what games are played with the second argument, simply because it accesses an array of size_t
as if it were an array of void*
.
Unions won't help, because assignment to one field of the union and then accessing a field of another type is not legal either. Exactly the same bad things that happen when dereferencing wrong pointers, happen in this case too.
The only semi-correct way to do access a value through an lvalue of different type is via memcpy
. Not that the results of that are automatically well-defined. A bit pattern that represents a valid value of type A may not represent a valid value of type B. But if this is not the case, then you can memcpy it and it is guaranteed to deliver the correct value.
Upvotes: 2