^^^ THIS QUESTION IS NOT ABOUT TYPE PUNNING ^^^
It is my understanding that an object contained in a union can only be used if it is active, and that it is active iff it was the last member to have a value stored to it. This suggests that the following code should be undefined at the points I mark.
My question is if I am correct in my understanding of when it is defined to access a member of a union, particularly in the following situations.
#include <stddef.h>
#include <stdio.h>
void work_with_ints(int* p, size_t k)
{
size_t i = 1;
for(;i<k;++i) p[i]=p[i-1];
}
void work_with_floats(float* p, size_t k)
{
size_t i = 1;
for(;i<k;++i) p[i]=p[i-1];
}
int main(void)
{
union{ int I[4]; float F[4]; } u;
// this is undefined because no member of the union was previously
// selected by storing a value to the union object
work_with_ints(u.I,4);
printf("%d %d %d %d\n",u.I[0],u.I[1],u.I[2],u.I[3]);
u.I[0]=1; u.I[1]=2; u.I[2]=3; u.I[3]=4;
// this is undefined because u currently stores an object of type int[4]
work_with_floats(u.F,4);
printf("%f %f %f %f\n",u.F[0],u.F[1],u.F[2],u.F[3]);
// this is defined because the assignment makes u store an object of
// type F[4], which is subsequently accessed
u.F[0]=42.0;
work_with_floats(u.F,4);
printf("%f %f %f %f\n",u.F[0],u.F[1],u.F[2],u.F[3]);
return 0;
}
Am I correct in the three items I have noted?
My actual example is not possible to use here due to size, but it was suggested in a comment that I extend this example to something compileable. I compiled and ran the above in both clang (-Weverything -std=c11) and gcc (-pedantic -std=c11). Each gave the following:
0 0 0 0
0.000000 0.000000 0.000000 0.000000
42.000000 42.000000 42.000000 42.000000
That seems appropriate, but that does not mean the code is compliant.
EDIT:
To clarify what the code is doing, I will point out the exact instances where the property I mention in the first paragraph is applied.
First, the contents of an uninitialized union are read and modified. This is undefined behavior, rather than unspecified with a potential for UB with traps, if the principle I mention in the first paragraph is true.
Second, the contents of a union are used with the type of an inactive union member. Again, this is undefined behavior, rather than unspecified with a potential for UB with traps, if the principle I mention in the first paragraph is true.
Third, the item just mentioned as "second" produces unspecified behavior with a potential for UB with traps, if first one element of the array contained in the inactive member is modified. This makes the whole array the active member, hence the change in definedness.
I am demonstrating the consequences of the principle in the first paragraph of this question, to show how that principle, if correct, affects the nature of the C standard. Consequent the significant effect on the nature of the standard in some circumstances, I am looking for help in determining if the principle I have stated is a correct understanding of the standard.
EDIT:
I think it may help to describe how I get from the standard the principle in the first paragraph above, and how one might disagree. Not much is said on the matter in the standard, so there has to be some filling in of the gaps no matter what.
The standard describes a union as holding one object at a time. This seems to suggest treating it like a structure containing one element. It seems that anything deviating from that interpretation deserves mention. That is how I get to the principle I have stated.
On the other hand, the discussion of effective type does not define the term "declared type". If that term is understood such that union members do not have a declared type, then it could be argued that each subobject of a union need be interpreted as another member recursively. So, in the last example in my code, all floating point array members would need to be initialized, not just the first.
The two examples I give of undefined behavior are important to me to resolve. However, the last example, which relates to the above paragraph, seems most crucial. I could really see an argument either way there.
EDIT:
This is not a type punning question. First, I am talking about writing to unions, not reading from them. Second, I am talking about the validity of doing these writes with a pointer rather than with the union type. This is very different from type punning issues.
This question is more related to strict aliasing than it is to type punning. You can not access memory however you want due to strict aliasing. This question deals with exactly how unions ease the constraints of strict aliasing on their members. It is not said they ever do that, but if they don't then you could never do something like the following.
union{int i} u; u.i=0; function_working_with_an_int_pointer (&u.i);
So, clearly, unions affect the application of strict aliasing rules in some cases. My question is to confirm that the line I have drawn according to my reading of the standard is correct.