Why is it invalid for a union type declared in one function to be used in another function?
Clash Royale CLAN TAG#URR8PPP
up vote
12
down vote
favorite
When I read ISO/IEC 9899:1999 (see:6.5.2.3), I saw an example like this:
The following is not valid fragment (because the union type is not visible within function f):
struct t1 int m; ;
struct t2 int m; ;
int f(struct t1 * p1, struct t2 * p2)
if (p1->m < 0)
p2->m = -p2->m;
return p1->m;
int g()
union
struct t1 s1;
struct t2 s2;
u;
/* ... */
return f(&u.s1, &u.s2);
I found no errors and warnings when I tested.
My question is: Why is this fragment invalid?
c
add a comment |Â
up vote
12
down vote
favorite
When I read ISO/IEC 9899:1999 (see:6.5.2.3), I saw an example like this:
The following is not valid fragment (because the union type is not visible within function f):
struct t1 int m; ;
struct t2 int m; ;
int f(struct t1 * p1, struct t2 * p2)
if (p1->m < 0)
p2->m = -p2->m;
return p1->m;
int g()
union
struct t1 s1;
struct t2 s2;
u;
/* ... */
return f(&u.s1, &u.s2);
I found no errors and warnings when I tested.
My question is: Why is this fragment invalid?
c
4
f
can assume thatp1 != p2
because they point to different types. and with optimization - readp1->m
value in register and return this register. it assume thatp2->m = -p2->m
not modifyp1->m
what is wrong. union here only way make p1==p2
– RbMm
3 hours ago
I took the liberty of transcribing the image to text, I hope I didn't make any typos. The original is visible in the edit history: stackoverflow.com/revisions/52511896/1
– ilkkachu
50 mins ago
add a comment |Â
up vote
12
down vote
favorite
up vote
12
down vote
favorite
When I read ISO/IEC 9899:1999 (see:6.5.2.3), I saw an example like this:
The following is not valid fragment (because the union type is not visible within function f):
struct t1 int m; ;
struct t2 int m; ;
int f(struct t1 * p1, struct t2 * p2)
if (p1->m < 0)
p2->m = -p2->m;
return p1->m;
int g()
union
struct t1 s1;
struct t2 s2;
u;
/* ... */
return f(&u.s1, &u.s2);
I found no errors and warnings when I tested.
My question is: Why is this fragment invalid?
c
When I read ISO/IEC 9899:1999 (see:6.5.2.3), I saw an example like this:
The following is not valid fragment (because the union type is not visible within function f):
struct t1 int m; ;
struct t2 int m; ;
int f(struct t1 * p1, struct t2 * p2)
if (p1->m < 0)
p2->m = -p2->m;
return p1->m;
int g()
union
struct t1 s1;
struct t2 s2;
u;
/* ... */
return f(&u.s1, &u.s2);
I found no errors and warnings when I tested.
My question is: Why is this fragment invalid?
c
c
edited 17 mins ago
muru
2,6651647
2,6651647
asked 3 hours ago


StrayedKing
946
946
4
f
can assume thatp1 != p2
because they point to different types. and with optimization - readp1->m
value in register and return this register. it assume thatp2->m = -p2->m
not modifyp1->m
what is wrong. union here only way make p1==p2
– RbMm
3 hours ago
I took the liberty of transcribing the image to text, I hope I didn't make any typos. The original is visible in the edit history: stackoverflow.com/revisions/52511896/1
– ilkkachu
50 mins ago
add a comment |Â
4
f
can assume thatp1 != p2
because they point to different types. and with optimization - readp1->m
value in register and return this register. it assume thatp2->m = -p2->m
not modifyp1->m
what is wrong. union here only way make p1==p2
– RbMm
3 hours ago
I took the liberty of transcribing the image to text, I hope I didn't make any typos. The original is visible in the edit history: stackoverflow.com/revisions/52511896/1
– ilkkachu
50 mins ago
4
4
f
can assume that p1 != p2
because they point to different types. and with optimization - read p1->m
value in register and return this register. it assume that p2->m = -p2->m
not modify p1->m
what is wrong. union here only way make p1==p2– RbMm
3 hours ago
f
can assume that p1 != p2
because they point to different types. and with optimization - read p1->m
value in register and return this register. it assume that p2->m = -p2->m
not modify p1->m
what is wrong. union here only way make p1==p2– RbMm
3 hours ago
I took the liberty of transcribing the image to text, I hope I didn't make any typos. The original is visible in the edit history: stackoverflow.com/revisions/52511896/1
– ilkkachu
50 mins ago
I took the liberty of transcribing the image to text, I hope I didn't make any typos. The original is visible in the edit history: stackoverflow.com/revisions/52511896/1
– ilkkachu
50 mins ago
add a comment |Â
2 Answers
2
active
oldest
votes
up vote
13
down vote
The example attempts to illustrate the paragraph beforehand1 (emphasis mine):
6.5.2.3 ¶6
One special guarantee is made in order to simplify the use of unions:
if a union contains several structures that share a common initial
sequence (see below), and if the union object currently contains one
of these structures, it is permitted to inspect the common initial
part of any of them anywhere that a declaration of the completed type
of the union is visible. Two structures share a common initial
sequence if corresponding members have compatible types (and, for
bit-fields, the same widths) for a sequence of one or more initial
members.
Since f
is declared before g
, and furthermore the unnamed union type is local to g
, there is no questioning the union type isn't visible in f
.
The example doesn't show how u
is initialized, but assuming the last written to member is u.s2.m
, the function has undefined behavior because it inspects p1->m
without the common initial sequence guarantee being in effect.
Same goes the other way, if it's u.s1.m
that was last written to before the function call, than accessing p2->m
is undefined behavior.
Note that f
itself is not invalid. It's a perfectly reasonable function definition. The undefined behavior stems from passing into it &u.s1
and &u.s2
as arguments. That is what's causing undefined behavior.
1 - I'm quoting n1570, the C11 standard draft. But the specification should be the same, subject only to moving a paragraph or two up/down.
So would changingf
to takeint*
and passing in&u.s1.m
andu.s2.m
make it valid? Because then it’sg
doing the struct accessing.
– Zastai
3 hours ago
1
@Zastai - You know, I'm not sure. I think that's a good question that deserves to stand on its own. Post it with the the language-lawyer tag. It should be interesting.
– StoryTeller
3 hours ago
@Zastai - Wait, actually you are right. Since those pointers are of the same type, they may alias the same object. It's the whole strict aliasing spiel.
– StoryTeller
2 hours ago
add a comment |Â
up vote
5
down vote
Here is the strict aliasing rule in action: one assumption made by the C (or C++) compiler, is that dereferencing pointers to objects of different types will never refer to the same memory location (i.e. alias each other.)
This function
int f(struct t1* p1, struct t2* p2);
assumes that p1 != p2
because they formally point to different types. As a result the optimizatier may assume that p2->m = -p2->m;
have no effect on p1->m
; it can first read the value of p1->m
to a register, compare it with 0, if it compare less than 0, then do p2->m = -p2->m;
and finally return the register value unchanged!
The union here is the only way to make p1 == p2
on binary level because all union member have the same address.
Another example:
struct t1 int m; ;
struct t2 int m; ;
int f(struct t1* p1, struct t2* p2)
if (p1->m < 0) p2->m = -p2->m;
return p1->m;
int g()
union
struct t1 s1;
struct t2 s2;
u;
u.s1.m = -1;
return f(&u.s1, &u.s2);
What must g
return? +1
according to common sense (we change -1 to +1 in f
). But if we look at gcc's generate assembly with -O1
optimization
f:
cmp DWORD PTR [rdi], 0
js .L3
.L2:
mov eax, DWORD PTR [rdi]
ret
.L3:
neg DWORD PTR [rsi]
jmp .L2
g:
mov eax, 1
ret
So far all is as excepted. But when we try it with -O2
f:
mov eax, DWORD PTR [rdi]
test eax, eax
js .L4
ret
.L4:
neg DWORD PTR [rsi]
ret
g:
mov eax, -1
ret
The return value is now a hardcoded -1
This is because f
at the beginning caches the value of p1->m
in the eax
register (mov eax, DWORD PTR [rdi]
) and does not reread it after p2->m = -p2->m;
(neg DWORD PTR [rsi]
) - it returns eax
unchanged.
union here used only for
all non-static data members of a union object have the same address. as result &u.s1 == &u.s2
.
is somebody not understand assembler code, can show in c/c++ how strict aliasing affect f code:
int f(struct t1* p1, struct t2* p2)
int a = p1->m;
if (a < 0) p2->m = -p2->m;
return a;
compiler cache p1->m
value in local var a
(actually in register of course) and return it , despite p2->m = -p2->m;
change p1->m
. but compiler assume that p1
memory not affected, because it assume that p2
point to another memory which not overlap with p1
so with different compilers and different optimization level the same source code can return different values (-1 or +1). so and undefined behavior as is
An example of generated assembly with GCC 8.2.
– StoryTeller
1 hour ago
@StoryTeller - yes, the same (-1) with clang. but with msvc and icc always +1 returned (no strict aliacing here)
– RbMm
1 hour ago
add a comment |Â
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
13
down vote
The example attempts to illustrate the paragraph beforehand1 (emphasis mine):
6.5.2.3 ¶6
One special guarantee is made in order to simplify the use of unions:
if a union contains several structures that share a common initial
sequence (see below), and if the union object currently contains one
of these structures, it is permitted to inspect the common initial
part of any of them anywhere that a declaration of the completed type
of the union is visible. Two structures share a common initial
sequence if corresponding members have compatible types (and, for
bit-fields, the same widths) for a sequence of one or more initial
members.
Since f
is declared before g
, and furthermore the unnamed union type is local to g
, there is no questioning the union type isn't visible in f
.
The example doesn't show how u
is initialized, but assuming the last written to member is u.s2.m
, the function has undefined behavior because it inspects p1->m
without the common initial sequence guarantee being in effect.
Same goes the other way, if it's u.s1.m
that was last written to before the function call, than accessing p2->m
is undefined behavior.
Note that f
itself is not invalid. It's a perfectly reasonable function definition. The undefined behavior stems from passing into it &u.s1
and &u.s2
as arguments. That is what's causing undefined behavior.
1 - I'm quoting n1570, the C11 standard draft. But the specification should be the same, subject only to moving a paragraph or two up/down.
So would changingf
to takeint*
and passing in&u.s1.m
andu.s2.m
make it valid? Because then it’sg
doing the struct accessing.
– Zastai
3 hours ago
1
@Zastai - You know, I'm not sure. I think that's a good question that deserves to stand on its own. Post it with the the language-lawyer tag. It should be interesting.
– StoryTeller
3 hours ago
@Zastai - Wait, actually you are right. Since those pointers are of the same type, they may alias the same object. It's the whole strict aliasing spiel.
– StoryTeller
2 hours ago
add a comment |Â
up vote
13
down vote
The example attempts to illustrate the paragraph beforehand1 (emphasis mine):
6.5.2.3 ¶6
One special guarantee is made in order to simplify the use of unions:
if a union contains several structures that share a common initial
sequence (see below), and if the union object currently contains one
of these structures, it is permitted to inspect the common initial
part of any of them anywhere that a declaration of the completed type
of the union is visible. Two structures share a common initial
sequence if corresponding members have compatible types (and, for
bit-fields, the same widths) for a sequence of one or more initial
members.
Since f
is declared before g
, and furthermore the unnamed union type is local to g
, there is no questioning the union type isn't visible in f
.
The example doesn't show how u
is initialized, but assuming the last written to member is u.s2.m
, the function has undefined behavior because it inspects p1->m
without the common initial sequence guarantee being in effect.
Same goes the other way, if it's u.s1.m
that was last written to before the function call, than accessing p2->m
is undefined behavior.
Note that f
itself is not invalid. It's a perfectly reasonable function definition. The undefined behavior stems from passing into it &u.s1
and &u.s2
as arguments. That is what's causing undefined behavior.
1 - I'm quoting n1570, the C11 standard draft. But the specification should be the same, subject only to moving a paragraph or two up/down.
So would changingf
to takeint*
and passing in&u.s1.m
andu.s2.m
make it valid? Because then it’sg
doing the struct accessing.
– Zastai
3 hours ago
1
@Zastai - You know, I'm not sure. I think that's a good question that deserves to stand on its own. Post it with the the language-lawyer tag. It should be interesting.
– StoryTeller
3 hours ago
@Zastai - Wait, actually you are right. Since those pointers are of the same type, they may alias the same object. It's the whole strict aliasing spiel.
– StoryTeller
2 hours ago
add a comment |Â
up vote
13
down vote
up vote
13
down vote
The example attempts to illustrate the paragraph beforehand1 (emphasis mine):
6.5.2.3 ¶6
One special guarantee is made in order to simplify the use of unions:
if a union contains several structures that share a common initial
sequence (see below), and if the union object currently contains one
of these structures, it is permitted to inspect the common initial
part of any of them anywhere that a declaration of the completed type
of the union is visible. Two structures share a common initial
sequence if corresponding members have compatible types (and, for
bit-fields, the same widths) for a sequence of one or more initial
members.
Since f
is declared before g
, and furthermore the unnamed union type is local to g
, there is no questioning the union type isn't visible in f
.
The example doesn't show how u
is initialized, but assuming the last written to member is u.s2.m
, the function has undefined behavior because it inspects p1->m
without the common initial sequence guarantee being in effect.
Same goes the other way, if it's u.s1.m
that was last written to before the function call, than accessing p2->m
is undefined behavior.
Note that f
itself is not invalid. It's a perfectly reasonable function definition. The undefined behavior stems from passing into it &u.s1
and &u.s2
as arguments. That is what's causing undefined behavior.
1 - I'm quoting n1570, the C11 standard draft. But the specification should be the same, subject only to moving a paragraph or two up/down.
The example attempts to illustrate the paragraph beforehand1 (emphasis mine):
6.5.2.3 ¶6
One special guarantee is made in order to simplify the use of unions:
if a union contains several structures that share a common initial
sequence (see below), and if the union object currently contains one
of these structures, it is permitted to inspect the common initial
part of any of them anywhere that a declaration of the completed type
of the union is visible. Two structures share a common initial
sequence if corresponding members have compatible types (and, for
bit-fields, the same widths) for a sequence of one or more initial
members.
Since f
is declared before g
, and furthermore the unnamed union type is local to g
, there is no questioning the union type isn't visible in f
.
The example doesn't show how u
is initialized, but assuming the last written to member is u.s2.m
, the function has undefined behavior because it inspects p1->m
without the common initial sequence guarantee being in effect.
Same goes the other way, if it's u.s1.m
that was last written to before the function call, than accessing p2->m
is undefined behavior.
Note that f
itself is not invalid. It's a perfectly reasonable function definition. The undefined behavior stems from passing into it &u.s1
and &u.s2
as arguments. That is what's causing undefined behavior.
1 - I'm quoting n1570, the C11 standard draft. But the specification should be the same, subject only to moving a paragraph or two up/down.
edited 3 hours ago
answered 3 hours ago
StoryTeller
83.7k12168235
83.7k12168235
So would changingf
to takeint*
and passing in&u.s1.m
andu.s2.m
make it valid? Because then it’sg
doing the struct accessing.
– Zastai
3 hours ago
1
@Zastai - You know, I'm not sure. I think that's a good question that deserves to stand on its own. Post it with the the language-lawyer tag. It should be interesting.
– StoryTeller
3 hours ago
@Zastai - Wait, actually you are right. Since those pointers are of the same type, they may alias the same object. It's the whole strict aliasing spiel.
– StoryTeller
2 hours ago
add a comment |Â
So would changingf
to takeint*
and passing in&u.s1.m
andu.s2.m
make it valid? Because then it’sg
doing the struct accessing.
– Zastai
3 hours ago
1
@Zastai - You know, I'm not sure. I think that's a good question that deserves to stand on its own. Post it with the the language-lawyer tag. It should be interesting.
– StoryTeller
3 hours ago
@Zastai - Wait, actually you are right. Since those pointers are of the same type, they may alias the same object. It's the whole strict aliasing spiel.
– StoryTeller
2 hours ago
So would changing
f
to take int*
and passing in &u.s1.m
and u.s2.m
make it valid? Because then it’s g
doing the struct accessing.– Zastai
3 hours ago
So would changing
f
to take int*
and passing in &u.s1.m
and u.s2.m
make it valid? Because then it’s g
doing the struct accessing.– Zastai
3 hours ago
1
1
@Zastai - You know, I'm not sure. I think that's a good question that deserves to stand on its own. Post it with the the language-lawyer tag. It should be interesting.
– StoryTeller
3 hours ago
@Zastai - You know, I'm not sure. I think that's a good question that deserves to stand on its own. Post it with the the language-lawyer tag. It should be interesting.
– StoryTeller
3 hours ago
@Zastai - Wait, actually you are right. Since those pointers are of the same type, they may alias the same object. It's the whole strict aliasing spiel.
– StoryTeller
2 hours ago
@Zastai - Wait, actually you are right. Since those pointers are of the same type, they may alias the same object. It's the whole strict aliasing spiel.
– StoryTeller
2 hours ago
add a comment |Â
up vote
5
down vote
Here is the strict aliasing rule in action: one assumption made by the C (or C++) compiler, is that dereferencing pointers to objects of different types will never refer to the same memory location (i.e. alias each other.)
This function
int f(struct t1* p1, struct t2* p2);
assumes that p1 != p2
because they formally point to different types. As a result the optimizatier may assume that p2->m = -p2->m;
have no effect on p1->m
; it can first read the value of p1->m
to a register, compare it with 0, if it compare less than 0, then do p2->m = -p2->m;
and finally return the register value unchanged!
The union here is the only way to make p1 == p2
on binary level because all union member have the same address.
Another example:
struct t1 int m; ;
struct t2 int m; ;
int f(struct t1* p1, struct t2* p2)
if (p1->m < 0) p2->m = -p2->m;
return p1->m;
int g()
union
struct t1 s1;
struct t2 s2;
u;
u.s1.m = -1;
return f(&u.s1, &u.s2);
What must g
return? +1
according to common sense (we change -1 to +1 in f
). But if we look at gcc's generate assembly with -O1
optimization
f:
cmp DWORD PTR [rdi], 0
js .L3
.L2:
mov eax, DWORD PTR [rdi]
ret
.L3:
neg DWORD PTR [rsi]
jmp .L2
g:
mov eax, 1
ret
So far all is as excepted. But when we try it with -O2
f:
mov eax, DWORD PTR [rdi]
test eax, eax
js .L4
ret
.L4:
neg DWORD PTR [rsi]
ret
g:
mov eax, -1
ret
The return value is now a hardcoded -1
This is because f
at the beginning caches the value of p1->m
in the eax
register (mov eax, DWORD PTR [rdi]
) and does not reread it after p2->m = -p2->m;
(neg DWORD PTR [rsi]
) - it returns eax
unchanged.
union here used only for
all non-static data members of a union object have the same address. as result &u.s1 == &u.s2
.
is somebody not understand assembler code, can show in c/c++ how strict aliasing affect f code:
int f(struct t1* p1, struct t2* p2)
int a = p1->m;
if (a < 0) p2->m = -p2->m;
return a;
compiler cache p1->m
value in local var a
(actually in register of course) and return it , despite p2->m = -p2->m;
change p1->m
. but compiler assume that p1
memory not affected, because it assume that p2
point to another memory which not overlap with p1
so with different compilers and different optimization level the same source code can return different values (-1 or +1). so and undefined behavior as is
An example of generated assembly with GCC 8.2.
– StoryTeller
1 hour ago
@StoryTeller - yes, the same (-1) with clang. but with msvc and icc always +1 returned (no strict aliacing here)
– RbMm
1 hour ago
add a comment |Â
up vote
5
down vote
Here is the strict aliasing rule in action: one assumption made by the C (or C++) compiler, is that dereferencing pointers to objects of different types will never refer to the same memory location (i.e. alias each other.)
This function
int f(struct t1* p1, struct t2* p2);
assumes that p1 != p2
because they formally point to different types. As a result the optimizatier may assume that p2->m = -p2->m;
have no effect on p1->m
; it can first read the value of p1->m
to a register, compare it with 0, if it compare less than 0, then do p2->m = -p2->m;
and finally return the register value unchanged!
The union here is the only way to make p1 == p2
on binary level because all union member have the same address.
Another example:
struct t1 int m; ;
struct t2 int m; ;
int f(struct t1* p1, struct t2* p2)
if (p1->m < 0) p2->m = -p2->m;
return p1->m;
int g()
union
struct t1 s1;
struct t2 s2;
u;
u.s1.m = -1;
return f(&u.s1, &u.s2);
What must g
return? +1
according to common sense (we change -1 to +1 in f
). But if we look at gcc's generate assembly with -O1
optimization
f:
cmp DWORD PTR [rdi], 0
js .L3
.L2:
mov eax, DWORD PTR [rdi]
ret
.L3:
neg DWORD PTR [rsi]
jmp .L2
g:
mov eax, 1
ret
So far all is as excepted. But when we try it with -O2
f:
mov eax, DWORD PTR [rdi]
test eax, eax
js .L4
ret
.L4:
neg DWORD PTR [rsi]
ret
g:
mov eax, -1
ret
The return value is now a hardcoded -1
This is because f
at the beginning caches the value of p1->m
in the eax
register (mov eax, DWORD PTR [rdi]
) and does not reread it after p2->m = -p2->m;
(neg DWORD PTR [rsi]
) - it returns eax
unchanged.
union here used only for
all non-static data members of a union object have the same address. as result &u.s1 == &u.s2
.
is somebody not understand assembler code, can show in c/c++ how strict aliasing affect f code:
int f(struct t1* p1, struct t2* p2)
int a = p1->m;
if (a < 0) p2->m = -p2->m;
return a;
compiler cache p1->m
value in local var a
(actually in register of course) and return it , despite p2->m = -p2->m;
change p1->m
. but compiler assume that p1
memory not affected, because it assume that p2
point to another memory which not overlap with p1
so with different compilers and different optimization level the same source code can return different values (-1 or +1). so and undefined behavior as is
An example of generated assembly with GCC 8.2.
– StoryTeller
1 hour ago
@StoryTeller - yes, the same (-1) with clang. but with msvc and icc always +1 returned (no strict aliacing here)
– RbMm
1 hour ago
add a comment |Â
up vote
5
down vote
up vote
5
down vote
Here is the strict aliasing rule in action: one assumption made by the C (or C++) compiler, is that dereferencing pointers to objects of different types will never refer to the same memory location (i.e. alias each other.)
This function
int f(struct t1* p1, struct t2* p2);
assumes that p1 != p2
because they formally point to different types. As a result the optimizatier may assume that p2->m = -p2->m;
have no effect on p1->m
; it can first read the value of p1->m
to a register, compare it with 0, if it compare less than 0, then do p2->m = -p2->m;
and finally return the register value unchanged!
The union here is the only way to make p1 == p2
on binary level because all union member have the same address.
Another example:
struct t1 int m; ;
struct t2 int m; ;
int f(struct t1* p1, struct t2* p2)
if (p1->m < 0) p2->m = -p2->m;
return p1->m;
int g()
union
struct t1 s1;
struct t2 s2;
u;
u.s1.m = -1;
return f(&u.s1, &u.s2);
What must g
return? +1
according to common sense (we change -1 to +1 in f
). But if we look at gcc's generate assembly with -O1
optimization
f:
cmp DWORD PTR [rdi], 0
js .L3
.L2:
mov eax, DWORD PTR [rdi]
ret
.L3:
neg DWORD PTR [rsi]
jmp .L2
g:
mov eax, 1
ret
So far all is as excepted. But when we try it with -O2
f:
mov eax, DWORD PTR [rdi]
test eax, eax
js .L4
ret
.L4:
neg DWORD PTR [rsi]
ret
g:
mov eax, -1
ret
The return value is now a hardcoded -1
This is because f
at the beginning caches the value of p1->m
in the eax
register (mov eax, DWORD PTR [rdi]
) and does not reread it after p2->m = -p2->m;
(neg DWORD PTR [rsi]
) - it returns eax
unchanged.
union here used only for
all non-static data members of a union object have the same address. as result &u.s1 == &u.s2
.
is somebody not understand assembler code, can show in c/c++ how strict aliasing affect f code:
int f(struct t1* p1, struct t2* p2)
int a = p1->m;
if (a < 0) p2->m = -p2->m;
return a;
compiler cache p1->m
value in local var a
(actually in register of course) and return it , despite p2->m = -p2->m;
change p1->m
. but compiler assume that p1
memory not affected, because it assume that p2
point to another memory which not overlap with p1
so with different compilers and different optimization level the same source code can return different values (-1 or +1). so and undefined behavior as is
Here is the strict aliasing rule in action: one assumption made by the C (or C++) compiler, is that dereferencing pointers to objects of different types will never refer to the same memory location (i.e. alias each other.)
This function
int f(struct t1* p1, struct t2* p2);
assumes that p1 != p2
because they formally point to different types. As a result the optimizatier may assume that p2->m = -p2->m;
have no effect on p1->m
; it can first read the value of p1->m
to a register, compare it with 0, if it compare less than 0, then do p2->m = -p2->m;
and finally return the register value unchanged!
The union here is the only way to make p1 == p2
on binary level because all union member have the same address.
Another example:
struct t1 int m; ;
struct t2 int m; ;
int f(struct t1* p1, struct t2* p2)
if (p1->m < 0) p2->m = -p2->m;
return p1->m;
int g()
union
struct t1 s1;
struct t2 s2;
u;
u.s1.m = -1;
return f(&u.s1, &u.s2);
What must g
return? +1
according to common sense (we change -1 to +1 in f
). But if we look at gcc's generate assembly with -O1
optimization
f:
cmp DWORD PTR [rdi], 0
js .L3
.L2:
mov eax, DWORD PTR [rdi]
ret
.L3:
neg DWORD PTR [rsi]
jmp .L2
g:
mov eax, 1
ret
So far all is as excepted. But when we try it with -O2
f:
mov eax, DWORD PTR [rdi]
test eax, eax
js .L4
ret
.L4:
neg DWORD PTR [rsi]
ret
g:
mov eax, -1
ret
The return value is now a hardcoded -1
This is because f
at the beginning caches the value of p1->m
in the eax
register (mov eax, DWORD PTR [rdi]
) and does not reread it after p2->m = -p2->m;
(neg DWORD PTR [rsi]
) - it returns eax
unchanged.
union here used only for
all non-static data members of a union object have the same address. as result &u.s1 == &u.s2
.
is somebody not understand assembler code, can show in c/c++ how strict aliasing affect f code:
int f(struct t1* p1, struct t2* p2)
int a = p1->m;
if (a < 0) p2->m = -p2->m;
return a;
compiler cache p1->m
value in local var a
(actually in register of course) and return it , despite p2->m = -p2->m;
change p1->m
. but compiler assume that p1
memory not affected, because it assume that p2
point to another memory which not overlap with p1
so with different compilers and different optimization level the same source code can return different values (-1 or +1). so and undefined behavior as is
edited 1 hour ago
answered 2 hours ago


RbMm
15.7k1923
15.7k1923
An example of generated assembly with GCC 8.2.
– StoryTeller
1 hour ago
@StoryTeller - yes, the same (-1) with clang. but with msvc and icc always +1 returned (no strict aliacing here)
– RbMm
1 hour ago
add a comment |Â
An example of generated assembly with GCC 8.2.
– StoryTeller
1 hour ago
@StoryTeller - yes, the same (-1) with clang. but with msvc and icc always +1 returned (no strict aliacing here)
– RbMm
1 hour ago
An example of generated assembly with GCC 8.2.
– StoryTeller
1 hour ago
An example of generated assembly with GCC 8.2.
– StoryTeller
1 hour ago
@StoryTeller - yes, the same (-1) with clang. but with msvc and icc always +1 returned (no strict aliacing here)
– RbMm
1 hour ago
@StoryTeller - yes, the same (-1) with clang. but with msvc and icc always +1 returned (no strict aliacing here)
– RbMm
1 hour ago
add a comment |Â
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f52511896%2fwhy-is-it-invalid-for-a-union-type-declared-in-one-function-to-be-used-in-anothe%23new-answer', 'question_page');
);
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
4
f
can assume thatp1 != p2
because they point to different types. and with optimization - readp1->m
value in register and return this register. it assume thatp2->m = -p2->m
not modifyp1->m
what is wrong. union here only way make p1==p2– RbMm
3 hours ago
I took the liberty of transcribing the image to text, I hope I didn't make any typos. The original is visible in the edit history: stackoverflow.com/revisions/52511896/1
– ilkkachu
50 mins ago