I guess I'm taking this part of the rule wording:
"If a program attempts to access the stored value of an object through ...
an aggregate type that includes the dynamic type of the object among its members ... [then the behaviour is well defined]"
In my interpretation, the stored value that I'm accessing is the value of
id. In every case, it's accessed through an aggregate that includes the actual type of
id.
So basically, I'm confused as to how the wording applies exactly when it comes to aggregates such as structures -- my above interpretation is focussed on the actual primitive type being accessed, whereas Bregma's interpretation focuses on the containing structures being different.
The point of strict-aliasing (
and the restrict keyword) is that when I write to an int, the compiler knows that only other int variables might have been invalidated by that write (and may need to be re-read). So I figure that when I write to
foo.id, which is an
Commands::Type, the compiler knows that
cmd->id might have been changed, because it's also a
Commands::Type, and the aggregate types that contains these members is irrelevant -- only the types being read/written matter.
Implementation wise (so far all systems I know) will work as expected because the compiler can see you casted Foo into Command. However, compile some code with -fstrict-aliasing where the compiler can't see in it's scope you did that, and you'll run into trouble, for example this:
That's a good bit of code to clarify my question -- switching over to C99, which has almost the same rule -- given the code:
struct A { int value; };
struct B { int value; };
void test( A* a, B* b )
{
a->value = 42;
print( b->value );
}
int main()
{
A obj = { 0 };
test( &obj, (B*)&obj );
}
Is the above code equivalent to test1 or test2 below?void test1( int* a, int* b )
{
*a = 42;
print( *b );
}
void test2( int* restrict a, int* restrict b )
{
*a = 42;
print( *b );
}
int main()
{
int obj1 = 0, obj2 = 0;
test1( &obj1, &obj1 );//should print 42
test2( &obj2, &obj2 );//might print 0 or 42 (or do something else)
}
And if the latter, does this mean that we can emulate the
restrict keyword simply by creating these kinds of wrapper structs?
And given Matias' example of soem broken code, it seems that this work around would make it well-defined, right?
void test( Command *cmd, Foo *foo )
{
//cmd->id = 1; //instead of this, which the compiler apparently knows can't change foo->id
int* id = &cmd->id;//int* is allowed to alias any int
*id = 1;//now foo->id might possibly be 1, legitimately!?
print( foo->id );//will print 1 (if (void*)cmd == (void*)foo)
}
Why on earth would one do such a thing? Why not just declaring the alignment explicitly through inheritance?
Yeah, in my actual implementation of this command system, I do use inheritance (
and in C you could use composition, where the Foo struct begins with an Id member).
However, aliasing structures is a common pattern, so I'd like to understand the rule in it's edge cases!
Also, I've seen some compilers where #1 and #2 would fail, but #3/#4/#5 would pass this test (
due to padding Base up to 4 bytes), which matters when structures have to match up with externally generated data formats:
struct Base { u8 id; };
struct Derived : public Base { u8 extra[3]; u32 extra2; };
struct DerivedHack { u8 id; u8 extra[3]; u32 extra2; };
#pragma pack(push)
#pragma pack(1)
struct BasePacked { u8 id; };
struct DerivedPacked : public BasePacked { u8 extra[3]; u32 extra2; };
#pragma pack(pop)
static_assert( sizeof(Base) == 1, "#1" );
static_assert( sizeof(Derived) == 8, "#2" );
static_assert( sizeof(DerivedHack) == 8, "#3" );
static_assert( sizeof(BasePacked) == 1, "#4" );
static_assert( sizeof(DerivedPacked) == 8, "#5" );