I've been trying to work out how legal the below is and I could really use some help.
#include <stdio.h>
#include <stdlib.h>
typedef struct foo {
int foo;
int bar;
} foo;
void make_foo(void * p)
{
foo * this = (foo *)p;
this->foo = 0;
this->bar = 1;
}
typedef struct more_foo {
int foo;
int bar;
int more;
} more_foo;
void make_more_foo(void * p)
{
make_foo(p);
more_foo * this = (more_foo *)p;
this->more = 2;
}
int main(void)
{
more_foo * mf = malloc(sizeof(more_foo));
make_more_foo(mf);
printf("%d %d %d\n", mf->foo, mf->bar, mf->more);
return 0;
}
As far as I've gathered, doing this is type punning and is supposed to violate the strict aliasing rule. Does it, though? The pointers passed around are void. You are allowed to interpret a void pointer any way you wish, correct?
Also, I read that there may be memory alignment issues. But struct alignment is deterministic. If the initial members are the same, then they'll get aligned the same way, and there should be no problems accessing all foo members from a more_foo pointer. Is that correct?
GCC compiles with -Wall without warnings, the program runs as expected. However, I'm not sure if it's UB or not and why.
I also saw that this:
typedef union baz {
struct foo f;
struct more_foo mf;
} baz;
void some_func(void)
{
baz b;
more_foo * mf = &b.mf; // or more_foo * mf = (more_foo *)&b;
make_more_foo(mf);
printf("%d %d %d\n", mf->foo, mf->bar, mf->more);
}
seems to be allowed. Because of the polymorphic nature of unions the compiler would be ok with it. Is that correct? Does that mean that by compiling with strict aliasing off you don't have to use an union and can use only structs instead?
Edit: union baz
now compiles.
union baz
is invalid and doesn't compile. Please fix. – Gypsymake_foo(p); more_foo * this = (more_foo *)p; this->more = 2;
as the compiler can ignore thatp
andthis
point to overlapped data. So the order of evaluation ofmake_foo(p);
andmore_foo * this = (more_foo *)p; this->more = 2;
can happen in either order or concurrent. Now the compiler could think it can use 2x wideint
access tothis
and over-write whatmake_foo(p);
did. Sounds contrived, yet that is AA. – Pincervoid*
can be changed to a character pointer or to its original pointer type. But if the pointer was originally the address of anint
, it may not covert to astruct foo*
. I do not think this issue negates your investigation here though. – Pincerint
access" ? The width of anint
is known. Even if the order of evaluation is switched the members do not overlap in memory. – Whiteside*this
and*p
do not overlap. Assume the compiler might have access to a fast wide 128-bit read instruction and read the entire*this
at once into a reg. (Compiler also paddedstruct more_foo
to 128 bits.) Doesthis->more = 2;
in the reg and writes it out back to*this
. At the same timemake_foo(p);
was occurring. The order of these two is indeterminate and UB per AA - as I understand it. – Pincerstruct *
is similarly encoded like otherstruct *
and they point to data in the same memory area that shares a common alignment requirement.char
objects may exist elsewhere with a different alignment and pointer representation. Same fordouble
object may exist elsewhere. – Pincermake_more_foo
as the only conversions are to/fromvoid *
. It's given avoid *
, passes to another function expecting avoid *
, then converts it to amore_foo *
and dereferences. – Therapeutic