Why is it invalid for a union type declared in one function to be used in another function?

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP











up vote
12
down vote

favorite
1












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?










share|improve this question



















  • 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










  • 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














up vote
12
down vote

favorite
1












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?










share|improve this question



















  • 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










  • 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












up vote
12
down vote

favorite
1









up vote
12
down vote

favorite
1






1





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?










share|improve this question















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






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited 17 mins ago









muru

2,6651647




2,6651647










asked 3 hours ago









StrayedKing

946




946







  • 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










  • 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




    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







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












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.






share|improve this answer






















  • 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




    @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


















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






share|improve this answer






















  • 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










Your Answer





StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");

StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "1"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);

else
createEditor();

);

function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
convertImagesToLinks: true,
noModals: false,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);



);













 

draft saved


draft discarded


















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






























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.






share|improve this answer






















  • 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




    @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















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.






share|improve this answer






















  • 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




    @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













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.






share|improve this answer














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.







share|improve this answer














share|improve this answer



share|improve this answer








edited 3 hours ago

























answered 3 hours ago









StoryTeller

83.7k12168235




83.7k12168235











  • 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




    @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






  • 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













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






share|improve this answer






















  • 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














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






share|improve this answer






















  • 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












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






share|improve this answer














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







share|improve this answer














share|improve this answer



share|improve this answer








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
















  • 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

















 

draft saved


draft discarded















































 


draft saved


draft discarded














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













































































Comments

Popular posts from this blog

What does second last employer means? [closed]

List of Gilmore Girls characters

Confectionery