@@ -41,10 +41,10 @@ ranges from ignoring the situation completely with unpredictable results, to
4141behavior during translation or program execution in a documented manner
4242characteristic of the environment (with or without the issuance of a
4343diagnostic message), to terminating a translation or execution (with the
44- issuance of a diagnostic message).
44+ issuance of a diagnostic message)."
4545
4646For some, those lines might be a bit hard to follow, but the
47- [ C FAQ] ( https://c-faq.com/ansi/undef.html ) nicely:
47+ [ C FAQ] ( https://c-faq.com/ansi/undef.html ) sums it up nicely:
4848
4949> Anything at all can happen; the Standard imposes no requirements.
5050> The program may fail to compile, or it may execute incorrectly (either
@@ -77,7 +77,7 @@ an example stating, "EXAMPLE An example of unspecified behavior is the order in
7777which the arguments to a function are evaluated."
7878
7979Summed up, the compiler team is free to do whatever they want in certain
80- scenarios when they face undefined behavior, and they never need to report it
80+ scenarios when they face unspecified behavior, and they never need to report it
8181or document it in any way.
8282
8383There are two more defined behaviors in the spec worth touching on briefly:
@@ -178,7 +178,8 @@ int main(void) {
178178This allows you to work with the color data at both the bit level and as
179179floating-point values without having to copy or cast the data. Cool, right?
180180As described earlier, this has caveats. Let's take a look at what the C99 and
181- C11 ISO specifications say about this.
181+ [C11 ISO](https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1548.pdf)
182+ specifications say about this.
182183
183184### The C99 and C11 Standard Definition
184185
@@ -219,7 +220,7 @@ undefined behavior when accessed.
219220
220221This change is important because it guarantees that type punning itself is not
221222undefined behavior according to the actual specification. However, there is a
222- subtle edge case not mentioned, and that is that reading an invalid object
223+ subtle edge case not mentioned, one being that reading an invalid object
223224representation resulting from the punning could still cause undefined behavior.
224225
225226To handle this, devs relied on `{0}` to zero the entire union. This was
@@ -231,7 +232,9 @@ valid memory state.
231232### The C++11 Standard Definition
232233
233234For completion's sake, let's take a look at what C++ states regarding unions.
234- We'll take a look at the C++14 ISO draft spec, specifically:
235+ We'll take a look at the
236+ [C++14 ISO draft spec](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4296.pdf),
237+ specifically:
235238
236239> In a union, at most one of the non-static data members can be active at any
237240> time, that is, the value of at most one of the non-static data members can be
@@ -280,12 +283,13 @@ set of rules applies to the other. They are different specs, after all.
280283
281284## Compiler Wars
282285
283- The two main compilers we tend to use these days in C are GNU GCC and Clang.
284- The latest news of GNU GCC 15.1 changing the behavior of zero'ing a union had
285- me check into what Clang is doing, and the most interesting this is that they
286- almost swapped their own unspecified behaviors.
286+ The two main compilers we tend to use these days in C are GNU GCC and
287+ [Clang](https://clang.llvm.org/). The latest news of GNU GCC 15.1 changing the
288+ behavior of zero'ing a union had me check into what Clang is doing, and the
289+ most interesting this is that they almost swapped their own unspecified
290+ behaviors.
287291
288- ### GCC (old) - Initialize Entire Union
292+ ### Creating an Example
289293
290294We'll start with the first base-case of GNU GCC and its historic behavior of
291295zero'ing a union. We'll use this example code:
@@ -319,10 +323,14 @@ int main(void) {
319323}
320324```
321325
326+ Now let's run this into [ godbolt] ( https://godbolt.org/ ) and see what happens!
327+
328+ ### GCC (old) - Initialize Entire Union
329+
322330The following is the resulting x86-64 assembly from GCC 11.1. We will focus
323331on the ` init_f ` assembly that is generated:
324332
325- ``` asm
333+ ``` bash
326334init_f:
327335 push rbp
328336 mov rbp, rsp
@@ -340,7 +348,7 @@ is the expected behavior devs have come to know.
340348
341349In memory, it would look something like this:
342350
343- ``` ascii
351+ ``` bash
344352Memory:
345353[00][00][00][00][00][00][00][00]
346354^^^^^^^^ first 4 bytes = float f
@@ -356,7 +364,7 @@ Result:
356364
357365Now, let's take a look at how GCC 15.1 handles it with the new change:
358366
359- ``` asm
367+ ``` bash
360368init_f:
361369 push rbp
362370 mov rbp, rsp
@@ -371,7 +379,7 @@ init_f:
371379
372380Here, we see the following:
373381
374- ``` asm
382+ ``` bash
375383pxor xmm0, xmm0
376384movss DWORD PTR [rax], xmm0
377385```
@@ -381,7 +389,7 @@ the full 8 bytes like we were back with GCC 11.1.
381389
382390In memory, it would look something like this:
383391
384- ``` ascii
392+ ``` bash
385393Memory:
386394[00][00][00][00][?? ][?? ][?? ][?? ]
387395^^^^^^^^ first 4 bytes = float f
@@ -399,7 +407,7 @@ is doing...
399407
400408Same code, same model. We'll take a look at Clang 6.0.0 first:
401409
402- ``` asm
410+ ``` bash
403411init_f:
404412 push rbp
405413 mov rbp, rsp
@@ -416,7 +424,7 @@ init_d:
416424
417425So this is interesting. The real key points are here:
418426
419- ``` asm
427+ ``` bash
420428xorps xmm0, xmm0 # zero xmm0
421429movss [rbp-16], xmm0 # write 4 bytes (float)
422430mov rax, [rbp-16]
@@ -433,7 +441,7 @@ see what the very latest version of Clang does.
433441
434442This is from Clang 20.1.0:
435443
436- ``` asm
444+ ``` bash
437445init_f:
438446 push rbp
439447 mov rbp, rsp
@@ -458,7 +466,7 @@ init_f:
458466
459467This is doing a lot more under the hood than before. The key point is here:
460468
461- ``` asm
469+ ``` bash
462470xorps xmm0, xmm0 # zero xmm0
463471movss [rbp-16], xmm0 # write 4 bytes (float)
464472
@@ -484,10 +492,10 @@ For anyone who wants to verify this, I created some godbolt links.
484492
485493## Where GCC Breaks Historical Behavior
486494
487- So here is where we get to the crux of the issue. For over a decade, C devs
488- have expected ` {0} ` to fully clear unions. Type punning is allowed in the newer
489- C standards, and compilers have gone to great lengths to try to maintain memory
490- safety by adhering to expected behavior, even if it is undefined or
495+ So here is where we get to the main point of the issue. For over a decade, C
496+ devs have expected ` {0} ` to fully clear unions. Type punning is allowed in the
497+ newer C standards, and compilers have gone to great lengths to try to maintain
498+ memory safety by adhering to expected behavior, even if it is undefined or
491499unspecified.
492500
493501The latest change in GCC's handling of this breaks what I consider to be
@@ -506,10 +514,14 @@ code that "worked fine" and are porting it to purpose-built embedded
506514Yocto-based distros will be the ones to start finding all these fun bugs.
507515
508516I completely understand why the GNU GCC team is allowed to do this given how
509- the spec reads. There's also no such thing as truly historical behavior in the
510- spec. However, there are certain things programmers rely on to be valid, even
511- if they aren't well-defined. This is one of those cases where I think the
512- compiler team might want to re-evaluate their decision.
517+ the spec reads. Many GCC devs claim that
518+ [ type punning via unions is undefined] ( https://gcc.gnu.org/bugzilla/show_bug.cgi?id=118141#c13 ) .
519+ There's also no such thing as truly historical behavior in the spec. However,
520+ there are certain things programmers rely on to be valid, even if they aren't
521+ well-defined. This is one of those cases where I think the compiler team might
522+ want to re-evaluate their decision, or at the very least present a solid
523+ argument as to why they are breaking away from a historical behavior that many
524+ of us have come to rely on, even if it was considered UB.
513525
514526## Further Reading
515527
0 commit comments