Given the following C code:
union Test {
struct {
int f1;
int f2;
};
struct {
int f3;
int f4;
int f5;
};
};
union Test test = {.f1 = 1, .f2 = 2};
When I compile this with gcc 6.1.1 f5 will be zero initialized. When I do with clang 3.8.0 it is not. I tried with -O0 and -O2 for both compilers which did not make any difference. This is on Linux x64.
Which is the correct behavior and can I tell clang to behave like gcc in this case? Reason is I try to compile some code with clang that assumes zero initialization in this case.
Update
Since the answers so far cite C11. Were there any changes in the standard that changed the behavior in later versions?
C11 specifies at section 6.2.6.1.7 :
When a value is stored in a member of an object of union type, the bytes of the object
representation that do not correspond to that member but do correspond to other members
take unspecified values.
You access the union via the first struct, accessing members of the second struct can produce unspecified values, so clang is not wrong neither is gcc.
Update: anonymous members were added in C11. Designated inits appeared in C99.
Related
I found that this code produces different results with "-fsanitize=undefined,address" and without it.
int printf(const char *, ...);
union {
long a;
short b;
int c;
} d;
int *e = &d.c;
int f, g;
long *h = &d.a;
int main() {
for (; f <= 0; f++) {
*h = g;
*e = 6;
}
printf("%d\n", d.b);
}
The command line is:
$ clang -O0 -fsanitize=undefined,address a.c -o out0
$ clang -O1 -fsanitize=undefined,address a.c -o out1
$ clang -O1 a.c -o out11
$ ./out0
6
$ ./out1
6
$ ./out11
0
The Clang version is:
$ clang -v
clang version 13.0.0 (/data/src/llvm-dev/llvm-project/clang 3eb2158f4fea90d56aeb200a5ca06f536c1df683)
Target: x86_64-unknown-linux-gnu
Thread model: posix
InstalledDir: /data/bin/llvm-dev/bin
Found candidate GCC installation: /opt/rh/devtoolset-7/root/usr/lib/gcc/x86_64-redhat-linux/7
Selected GCC installation: /opt/rh/devtoolset-7/root/usr/lib/gcc/x86_64-redhat-linux/7
Candidate multilib: .;#m64
Candidate multilib: 32;#m32
Selected multilib: .;#m64
Found CUDA installation: /usr/local/cuda, version 10.2
The OS and platform are:
CentOS Linux release 7.8.2003 (Core).0, x86_64 GNU/Linux
My questions:
Is there something wrong with my code? Is taking the address of multiple members of the union invalid in C?
If there is something wrong with my code, how do I get LLVM (or GCC) to warn me? I have used -Wall -Wextra but LLVM and GCC show no warning.
Is there something wrong with the code?
For practical purposes, yes.
I think this is the same underlying issue as Is it undefined behaviour to call a function with pointers to different elements of a union as arguments?
As Eric Postpischil points out, the C standard as read literally seems to permit your code, and require it to print out 6 (assuming that's consistent with how your implementation represents integer types and how it lays out unions). However, this literal reading would render the strict aliasing rule almost entirely impotent, so in my opinion it's not what the standard authors would have intended.
The spirit of the strict aliasing rule is that the same object may not be accessed through pointers to different types (with certain exceptions for character types, etc) and that the compiler may optimize on the assumption that this never happens. Although d.a and d.c are not strictly speaking "the same object", they do have overlapping storage, and I think compiler authors interpret the rule as also not allowing overlapping objects to be accessed through pointers to different types. Under that interpretation your code would have undefined behavior.
In Defect Report 236 the committee considered a similar example and stated that it has undefined behavior, because of its use of pointers that "have different types but designate the same region of storage". However, wording to clarify this does not seem to have ever made it into any subsequent version of the standard.
Anyhow, I think the practical upshot is that you cannot expect your code to work "correctly" under modern compilers that enforce their interpretations of the strict aliasing rule. Whether or not this is a clang bug is a matter of opinion, but even if you do think it is, then it's a bug that they are probably not ever going to fix.
Why does it behave this way?
If you use the -fno-strict-aliasing flag, then you get back to the 6 behavior. My guess is that the sanitizers happen to inhibit some of these optimizations, which is why you don't see the 0 behavior when using those options.
What seems to have happened under the hood with -O1 is the compiler assumed that the stores to *h and *e don't interact (because of their different types) and therefore can be freely reordered. So it hoisted *h = g outside the loop, since after all multiple stores to the same address, with no intervening load, are redundant and only the last one needs to be kept. It happened to put it after the loop, presumably because it can't prove that e doesn't point to g, so the value of g needs to be reloaded after the loop. So the final value of d.b is derived from *h = g which effectively does d.a = 0.
How to get a warning?
Unfortunately, compilers are not good at checking, either statically or at runtime, for violations of (their interpretation of) the strict aliasing rule. I'm not aware of any way to get a warning for such code. With clang you can use -Weverything to enable every warning option that it supports (many of which are useless or counterproductive), and even with that, it gives no relevant warnings about your program.
Another example
In case anyone is curious, here's another test case that doesn't rely on any type pun, reinterpretation, or other implementation-defined behavior.
#include <stdio.h>
short int zero = 0;
void a(int *pi, long *pl) {
for (int x = 0; x < 1000; x++) {
*pl = x;
*pi = zero;
}
}
int main(void) {
union { int i; long l; } u;
a(&u.i, &u.l);
printf("%d\n", u.i);
}
Try on godbolt
As read literally, this code would appear to print 0 on any implementation: the last assignment in a() was to u.i, so u.i should be the active member, and the printf should output the value 0 which was assigned to it. However, with clang -O2, the stores are reordered and the program outputs 999.
Just as a counterpoint, though, if you read the standard so as to make the above example UB, then this leads to the somewhat absurd conclusion that u.l = 0; u.i = 5; print(u.i); is well defined and prints 5, but that *&u.l = 0; *&u.i = 5; print(u.i); is UB. (Recall that the "cancellation rule" of & and * applies to &*p but not to *&x.)
The whole situation is rather unsatisfactory.
I will rewrite the code for ease of reading:
int printf(const char *, ...);
union
{
long l;
short s;
int i;
} u;
long *ul = &u.l;
int *ui = &u.i;
int counter, zero;
int main(void)
{
for (; counter <= 0; counter++)
{
*ul = zero;
*ui = 6;
}
printf("%d\n", u.s);
}
The only questionable code here is the use of u.s in the printf, when u.s is not the last member of the union that was stored. That is defined by C 2018 6.5.2.3, which says the value of u.s is that of the named member, and note 99 clarifies this means that, if s is not the member last used to store a value, the appropriate bytes are reinterpreted as a short. This is well established.
The other code is ordinary: *ul = zero; stores a value in a union member. There is no aliasing violating because ul points to a long and is used to access a long. *ui = 6; stores a value in another union member and is also not an aliasing violation.
The specific bytes used to represent 6 in an int are implementation-defined in regard to ordering and padding bits. However, whatever they are, they should be the same with or without Clang’s “sanitization” and the same in optimization levels 0 and 1. Therefore, the same result should be obtained in all compilations.
This is a compiler bug.
I agree with other comments and answer that this is likely a defect in the C standard, as it makes the aliasing rule largely useless. Nonetheless, the sample code conforms to the requirements of the C standard and ought to work as described.
If it happens and we initialize the union with two values I know that it will take the int number but I really want to know what happens behind the scenes
#include <stdio.h>
typedef union x
{
int y;
char x[6];
};
int main(void)
{
union x first={4,"AAAAAA"};
printf("%d\n",first.y);
printf("%s\n",first.x);
return 0;
}
Did this compile? it should not. the memory allocated will be written over for every time you repurpose the union.
You can define a union with many members, but only one member can contain a value at any given time. Unions provide an efficient way of using the same memory location for multiple-purpose.
The code in the question should not compile without at least a diagnostic about 'too many initializers' for the union variable. You might also get a warning about a useless storage class specifier in empty declaration because the typedef doesn't actually define an alias for union x.
Suppose you revised the code to use designated initializers, like this:
#include <stdio.h>
union x
{
int y;
char x[6];
};
int main(void)
{
union x first = { .y = 4, .x = "AAAAA" };
printf("%d\n", first.y);
printf("%s\n", first.x);
return 0;
}
This would compile and run, but with the compiler set to fussy, you might get warnings like warning: initialized field overwritten [-Woverride-init].
Note that there is one less A in the initializer for .x shown above than in the original. That ensures that the value is a (null-terminated) string, not just an array of bytes. In this context, the designated initializer for .x overrides the designated initializer for .y, and therefore the value in .x is fully valid. The output I got, for example, was:
1094795585
AAAAA
The decimal number corresponds to hex 0x41414141 as might be expected.
Note that I removed the pointless typedef. My default compilation rules wouldn't accept the code; I had to cancel -Werror and -Wextra options to get it to compile. The original code compiled with warnings without the -Werror to convert the warnings into error. Even adding -pedantic didn't trigger an error for the extra initializer (though the warning was always given, as required by the standard).
I have declared a flexible array member in union, like this:
#include <stdio.h>
union ut
{
int i;
int a[]; // flexible array member
};
int main(void)
{
union ut s;
return 0;
}
and compiler gives an error :
source_file.c:8:9: error: flexible array member in union
int a[];
But, Declared array zero size like this :
union ut
{
int i;
int a[0]; // Zero length array
};
And it's working fine.
Why does zero length array work fine union?
No, unions do not support flexible array members, only structs. C11 6.7.2.1 §18
As a special case, the last element of a structure with more than one
named member may have an incomplete array type; this is called a
flexible array member.
In addition, zero-length arrays is not valid C, that's a gcc non-standard extension. The reason why you get this to work is because your compiler, gcc, is configured to compile code for the "non-standard GNU language". If you would rather have it compile code for the C programming language, you need to add compiler options -std=c11 -pedantic-errors.
int a[] is the C standard notation (since C99).
int a[0] is GNU C syntax, which predates C99. Other compilers might also support it, I don't know.
Your compiler seems to default to C90 standard with GNU extensions, which is why latter compiles, but first one does.
Furthermore, as stated in Lundin's answer, standard C does not support flexible array members in union at all.
Try adding -std=c99 or -std=c11 to your compiler options (gcc docs here).
Also -pedantic or -pedantic-errors is probably a good idea too, it'll enforce more strict standard compliance.
And, obligatory aside, -Wall -Wextra won't hurt either...
I'm not sure what the standard would say about this, but G++' unions seems to accept flexible arrays just fine. If you wrap them in an anonymous struct first, like so:
union {
unsigned long int ul;
char fixed[4][2];
struct {
char flexible[][2];
};
};
The following code snippet is an example from the C11 standard §6.5.2.3:
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);
}
As per C11, the last line inside g() is invalid. Why so?
The example comes from Example 3 in §6.5.2.3 Structure and union members of ISO/IEC 9899:2011. One of the prior paragraphs is (emphasis added):
¶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.
The code quoted in the question is preceded by the comment:
The following is not a valid fragment (because the union type is not visible within function f).
This now makes sense in light of the highlighted statement. The code in g() is making use of the common initial sequence, but that only applies where the union is visible and it isn't visible in f().
The issue is also one of strict aliasing. That's a complex topic. See What is the strict aliasing rule? for the details.
For whatever it is worth, GCC 7.1.0 doesn't report the problem even under stringent warning options. Neither does Clang, even with the -Weverything option:
clang -O3 -g -std=c11 -Wall -Wextra -Werror -Wmissing-prototypes \
-Wstrict-prototypes -Weverything -pedantic …
This is because of the "effective type" rule. If you see f isolated, the two arguments have different type, and the compiler is allowed to do certain optimizations.
Here, p1 is accessed twice. If p1 and p2 are supposed to be different, the compiler needs not to reload p1's value for the return since it cannot have changed.
f is valid code, and the optimization is valid.
Calling it with the same object, in g, is not valid, because without seeing that both may come from the same union the compiler may not take provisions to avoid the optimization.
This is one of the cases, where the whole burden to prove that a call is valid lays on the user of a function, generally no compiler can warn you about this if f and g happen to be in different translation units.
I'm having some trouble with -Wpadded using C11 and structs.
I've already read Structure member alignment with _Alignas, and I looked in the clang docs and saw that it IS supported now.
Also, I'm using a very new version of clang that I built from trunk recently.
$ clang --version
clang version 3.3 (trunk 175473)
Target: x86_64-unknown-linux-gnu
Thread model: posix
The problem I'm running into is this:
#include <stdlib.h>
#include <stdalign.h>
struct foo{
void* a;
int b;
};
int main() {
struct foo instance;
instance.a = NULL;
instance.b = 2;
return 0;
}
Which throws me this warning:
$ clang -Weverything -std=c11 t.c
t.c:4:8: warning: padding size of 'struct foo' with 4 bytes to alignment boundary [-Wpadded]
struct foo{
^
1 warning generated.
Now isn't this what _Alignas is for? I tried putting it before the int member declaration, like so:
struct foo{
void* a;
_Alignas(void*) int b;
};
But the same warning remains. I also tried putting the _Alignas in various places, to no avail. What am I missing here?
I know I could just ignore this particular warning and I understand why padding is important, so I'm not interested in workarounds or explanations about what padding is. I want to know how to change my C in a portable, standards conformant way so that the warning is no longer emitted.
-Weverything prints all diagnostic messages required by C as well as some diagnostics not required by C. The diagnostic that is printed here is not required by C: its purpose is informative and your program is already strictly conforming. C says an implementation is free to produce additional diagnostic messages as long as it does not fail to translate the program.