Skip to content

Commit bd932f3

Browse files
committed
Various typo changes, fixes, more links for sources, etc.
1 parent 85d33f8 commit bd932f3

File tree

1 file changed

+40
-28
lines changed

1 file changed

+40
-28
lines changed

_posts/2025-04-27-undefined-behavior-vs-expected-behavior.md

Lines changed: 40 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,10 @@ ranges from ignoring the situation completely with unpredictable results, to
4141
behavior during translation or program execution in a documented manner
4242
characteristic of the environment (with or without the issuance of a
4343
diagnostic message), to terminating a translation or execution (with the
44-
issuance of a diagnostic message).
44+
issuance of a diagnostic message)."
4545

4646
For 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
7777
which the arguments to a function are evaluated."
7878

7979
Summed 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
8181
or document it in any way.
8282

8383
There are two more defined behaviors in the spec worth touching on briefly:
@@ -178,7 +178,8 @@ int main(void) {
178178
This allows you to work with the color data at both the bit level and as
179179
floating-point values without having to copy or cast the data. Cool, right?
180180
As 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
220221
This change is important because it guarantees that type punning itself is not
221222
undefined 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
223224
representation resulting from the punning could still cause undefined behavior.
224225
225226
To 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
233234
For 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
290294
We'll start with the first base-case of GNU GCC and its historic behavior of
291295
zero'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+
322330
The following is the resulting x86-64 assembly from GCC 11.1. We will focus
323331
on the `init_f` assembly that is generated:
324332

325-
```asm
333+
```bash
326334
init_f:
327335
push rbp
328336
mov rbp, rsp
@@ -340,7 +348,7 @@ is the expected behavior devs have come to know.
340348

341349
In memory, it would look something like this:
342350

343-
```ascii
351+
```bash
344352
Memory:
345353
[00][00][00][00][00][00][00][00]
346354
^^^^^^^^ first 4 bytes = float f
@@ -356,7 +364,7 @@ Result:
356364

357365
Now, let's take a look at how GCC 15.1 handles it with the new change:
358366

359-
```asm
367+
```bash
360368
init_f:
361369
push rbp
362370
mov rbp, rsp
@@ -371,7 +379,7 @@ init_f:
371379

372380
Here, we see the following:
373381

374-
```asm
382+
```bash
375383
pxor xmm0, xmm0
376384
movss DWORD PTR [rax], xmm0
377385
```
@@ -381,7 +389,7 @@ the full 8 bytes like we were back with GCC 11.1.
381389

382390
In memory, it would look something like this:
383391

384-
```ascii
392+
```bash
385393
Memory:
386394
[00][00][00][00][??][??][??][??]
387395
^^^^^^^^ first 4 bytes = float f
@@ -399,7 +407,7 @@ is doing...
399407

400408
Same code, same model. We'll take a look at Clang 6.0.0 first:
401409

402-
```asm
410+
```bash
403411
init_f:
404412
push rbp
405413
mov rbp, rsp
@@ -416,7 +424,7 @@ init_d:
416424

417425
So this is interesting. The real key points are here:
418426

419-
```asm
427+
```bash
420428
xorps xmm0, xmm0 # zero xmm0
421429
movss [rbp-16], xmm0 # write 4 bytes (float)
422430
mov rax, [rbp-16]
@@ -433,7 +441,7 @@ see what the very latest version of Clang does.
433441

434442
This is from Clang 20.1.0:
435443

436-
```asm
444+
```bash
437445
init_f:
438446
push rbp
439447
mov rbp, rsp
@@ -458,7 +466,7 @@ init_f:
458466

459467
This is doing a lot more under the hood than before. The key point is here:
460468

461-
```asm
469+
```bash
462470
xorps xmm0, xmm0 # zero xmm0
463471
movss [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
491499
unspecified.
492500

493501
The 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
506514
Yocto-based distros will be the ones to start finding all these fun bugs.
507515

508516
I 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

Comments
 (0)