URI:
       [HN Gopher] C++26: Trivial infinite loops are no longer undefine...
       ___________________________________________________________________
        
       C++26: Trivial infinite loops are no longer undefined behaviour
        
       Author : ibobev
       Score  : 161 points
       Date   : 2026-09-17 20:52 UTC (1 days ago)
        
  HTML web link (www.sandordargo.com)
  TEXT w3m dump (www.sandordargo.com)
        
       | MiroslavPokorny wrote:
       | Breadcrumbs for "blog", "year", "month" etc are broken and give
       | 404s :(
       | 
       | One can browse other blog entries so it really doesnt matter too
       | much.
        
       | Aurornis wrote:
       | I never would have guessed that the unreachable() function would
       | get executed in that example. Probably not something you'd
       | encounter in practice, though I have seen some weird things
       | happen with layers of #ifdef
        
         | saghm wrote:
         | That's kind of what you get with UB; the compiler doesn't need
         | to do what you expect.
        
         | mathisfun123 wrote:
         | > Probably not something you'd encounter in practice
         | 
         | it's actually probably the most common footgun you'll encounter
         | in practice: non-void functions with no return statements just
         | keep executing past their end. ask me how i know.
         | 
         | compile with -Wreturn-type if you want to avoid such things...
        
           | kouosi wrote:
           | > compile with -Wreturn-type if you want to avoid such
           | things...
           | 
           | Isn't -Wreturn-type enabled by default in both gcc and clang
           | atleast for c++?
        
             | mathisfun123 wrote:
             | probably - i guess then i meant -Werror=return-type
        
           | drdexebtjl wrote:
           | C on the other hand puts an implicit `return 0` at the end,
           | but only on the main function for some reason. Very weird.
        
             | mpyne wrote:
             | C++ also special-cases the `main` function. Probably
             | because `main` is the interface to the OS so it gets
             | special language treatment.
        
               | mitxela wrote:
               | In C++ you're not allowed to call main yourself, so that
               | the compiler can call the global constructors at the
               | start of main.
        
               | pjmlp wrote:
               | Which also exist as C extensions, in some compilers.
        
           | mitxela wrote:
           | In C. In C++ it's an error to not return, as it should be.
        
             | mathisfun123 wrote:
             | .... The blog post literally demonstrates that it is not.
             | Feel free to repro on your own machine.
        
               | mitxela wrote:
               | The main function in the blog post returns at the end of
               | all control paths. Firstly because main has an implicit
               | "return 0" at the end and secondly because no control
               | paths end. It doesn't jump to unreachable because it
               | lacks a return - it jumps there because that's how this
               | implementation has chosen to compile UB, despite the
               | presence of all necessary returns.
        
       | BobbyTables2 wrote:
       | TLDR: For almost 1/6 of a century, the C++ _standards_ broke the
       | simplest infinite loop and only just recently fixed it.
       | 
       | Idiots!
       | 
       | Don't they really that people write real programs to solve real
       | problems? This isn't a theoretical academic exercise!
        
         | bryanlarsen wrote:
         | They also realized that people choose compilers based on
         | performance benchmarks, and that insane optimizations let them
         | win.
        
           | vlovich123 wrote:
           | Until Rust proved actually you can get really good or better
           | performance if the language itself is better. I really don't
           | know how C++ digs itself out of the UB hole it has dug.
        
             | bryanlarsen wrote:
             | Probably by working together with Rust. Eliminating
             | undefined behavior from unsafe Rust is a big deal for the
             | Rust community at the moment. And given that most unsafe
             | rust code exists to call into C or C++, concepts like
             | pointer provenance need to be extended. And proper pointer
             | provenance guarantees can both decrease UB and increase
             | optimization potential.
             | 
             | IIUC, my understanding is shallow.
        
               | nicoburns wrote:
               | This particular case is likely an example of that. Rust
               | used to have this problem, but it _wasn 't_ ever intended
               | to. So IIRC it got fixed in LLVM for Rust, and this is
               | probably now C++ taking advantage of that.
        
               | vlovich123 wrote:
               | That's a niche level thing that helps in some scenarios,
               | and generally not as much for C++ which is much more
               | weakly typed than Rust is. Weak typing + static typing is
               | why safety problems in C++ are going to be really
               | difficult to fix without fundamentally changing the
               | language.
        
               | bryanlarsen wrote:
               | I expect some changes to the language from this
               | direction, some way to attach provenance information or
               | limitations to a pointer. Presumably through a #pragma at
               | first. Strict typing in the C++ sense, not the Rust
               | sense. An annotation like "volatile".
               | 
               | Pointer provenance is just one example, there are others.
        
         | Sharlin wrote:
         | The argument is that an infinite loop _without side effects_
         | isn 't a real program. It's not useful for anything except
         | wasting cycles.
        
           | kibwen wrote:
           | And unfortunately that argument would be incorrect, because
           | not only is there a realistic chance of hitting this on
           | embedded systems, the fact that LLVM baked this into its low-
           | level semantics resulted in miscompilations in Rust for a
           | time, where `loop {}` is a valid way to implement a diverging
           | function: https://github.com/rust-lang/rust/issues/28728
        
           | nh2 wrote:
           | Of course the infinite loop should run as expected.
           | 
           | It breaks the most fundamental debugging expectations (such
           | as "delete code until problem disappears") if the
           | fundamental, minimal building blocks of a language, when on
           | their own, do random rubbish.
           | 
           | To understand a program that does something, better first
           | understand a program that does nothing.
           | 
           | As a fan of sensible analogies:
           | 
           | You put a salad bowl with vinegar into the fridge and notice
           | that when you do that, the fridge stinks afterwards. You try
           | again without the vinegar, then without the salad. In C++
           | world, upon receiving the empty bowl, the fridge detonates
           | ("it is not useful"), blowing up your house. That is not OK.
        
             | Jaxan wrote:
             | But if you program a for loop computing the sum from 1 to
             | n, this also gets replaced by a constant (unless you build
             | in debug mode). Why would an empty loop be different?
        
               | vlovich123 wrote:
               | I think the argument is that the equivalent of an
               | infinite loop would be a halt / abort instruction, not a
               | complete removal of the loop and continue running
               | anything else.
        
               | rcxdude wrote:
               | Not necessarily what you want in that case either: it's a
               | common pattern in cases where you want the system to halt
               | until you can attach a debugger to inspect the state. A
               | halt/abort instruction that trashes that state would be
               | undesirable (some CPUs have an instruction that is
               | equivalent, but many do not, after all, why bother if you
               | can just write an infinite do-nothing loop?).
        
               | teo_zero wrote:
               | Expecting a piece of code to be compiled to a precise
               | sequence of machine instructions is exactly what you
               | should not do with high-level languages like C++. Their
               | task is exactly to abstract the machine away. They give
               | you the guarantee that the final observable result will
               | be what you asked for, not that the means to obtain that
               | result will be what you have in mind.
               | 
               | If you write a loop to zero out some memory, it can be
               | compiled to a loop, or to a call to an optimized
               | predefined function, or even to a sequence of single
               | zeroing instructions, if the size is small enough.
               | 
               | Even a single statement as a=0 may be compiled to a "load
               | immediate" instruction, or an "XOR with itself", or a
               | "sub with itself", or a move from another register known
               | to be 0.
        
               | rcxdude wrote:
               | I'm not sure what this has to do with my comment. I am
               | aware of all of this. It would be nice if an infinite
               | loop was defined to be 'do nothing, indefinitely'. On
               | architectures with an instruction that works that works
               | precisely that way, it could be turned into that,
               | perhaps, but that's an implementation detail and should
               | follow the as-if principle (I'm also not sure any
               | compiler would bother).
        
           | lou1306 wrote:
           | Yeah the argument here is clear, also rather silly. Either
           | you must accept that your language allows for completely
           | useless computation, or, if the compiler is so good at
           | detecting "unreal programs" it should also refuse to compile
           | them.
        
         | bluGill wrote:
         | You are an idiot if you write an infinite loop. An infinite
         | loop is a waste of CPU cycles and energy when run.
         | 
         | If it wasn't so hard to detect (the trivial cases are easy, but
         | it gets hard quickly) I'd say the program should fail to
         | compile.
        
           | echoangle wrote:
           | And how would you generate assembly to keep a microcontroller
           | idle then?
        
             | bluGill wrote:
             | You call the CPU halt instruction.
        
               | echoangle wrote:
               | What if my CPU doesn't have that? I don't think Atmel
               | Microcontrollers do for example.
        
               | bluGill wrote:
               | Better CPU selection. Embedded almost always have power
               | requirements and you need to put your CPU into a low
               | power mode not a loop which is running fast. You can also
               | design your hardware such that you can turn the power off
               | completely in these cases (or perhaps reboot).
               | 
               | Now that I think of it, a different project (I worked
               | just down the aisle, but I wasn't on it) solved a lot
               | customer complaints by turning all the "while(1);" loops
               | into blink an error code - which since it does IO is
               | defined behavior. Which probably is the correct answer to
               | your question - don't just spin doing nothing, spin in
               | such a way that the user has a clue why nothing is
               | working (and in turn you can find out and perhaps fix
               | real world bugs)
        
               | dare944 wrote:
               | This is myopic. In many cases it takes time, and
               | sometimes considerable programming effort, to enter and
               | exit low power modes. So you don't do it willy-nilly; you
               | do it when you believe the system has quiesced. That
               | means, on a purely interrupt driven system that is not
               | yet ready to sleep, the code may very well be spinning in
               | an empty infinite loop somewhere.
               | 
               | There's no need to inform the "user" because there's
               | nothing wrong with the system. Its simply waiting until
               | the benefit of sleeping outweighs the cost of getting
               | there.
        
               | bluGill wrote:
               | The context here is an infinite loop with no side
               | effects. You have all the time needed to enter those
               | states.
        
               | echoangle wrote:
               | Not necessarily, you could also want to make an infinite
               | loop and wait for an interrupt without eanting to power
               | down.
        
               | dare944 wrote:
               | It may take 100s of instructions to enter a deep sleep
               | state, and 100s to 1000s more to exit it. If you
               | anticipate having to service an event sooner than that,
               | you don't enter sleep at all (especially since there's
               | likely a point at which you're committed to sleep, and
               | have to go all the way down in order to come right back
               | out again). Instead, you hang around twiddling your
               | thumbs until the event comes along.
               | 
               | Now if your processor has a halt/wait-for-interrupt
               | instruction (most do but some don't) you can escape into
               | assembly and use that. But it probably makes little to no
               | difference to energy utilization, and of course its not
               | portable. A nice while (true); would seem obvious, except
               | that the C++ committee insisted that it wasn't.
               | 
               | Just one example of where the committee lost sight of the
               | fact that it was defining an imperative programming
               | language.
        
               | AMDmi3 wrote:
               | AVRs have SLEEP instruction.
        
           | dare944 wrote:
           | And where to you think all that "wasted" energy/cycles would
           | go otherwise? Why do you presume there's some other, more
           | efficient way the CPU could be spending its time while
           | waiting for an event to process?
           | 
           | I, the programmer, will decide what cycles are wasted or not.
           | That the C++ committee thought they knew better is hubris.
        
             | bluGill wrote:
             | If the CPU halts it isn't used at all. If the CPU does an
             | infinite loop then it all goes to heat.
             | 
             | If the loop is doing anything then it cannot be optimized
             | away. Only loops with no side effects meaning they are just
             | turning the CPU into a heater count.
        
           | AnimalMuppet wrote:
           | Non-trivial infinite loops are very much _not_ an  "idiot"
           | thing on embedded systems. "Run until power off" or "run
           | until the warhead detonates" are perfectly normal things to
           | do in that world.
        
         | Panzerschrek wrote:
         | > the simplest infinite loop
         | 
         | An infinite loop which does nothing is practically useless. So,
         | compilers optimize it out. That's the whole philosophy of
         | modern compilers - to reduce execution time by preserving
         | semantics. In case of an infinite loop elimination it's an
         | optimization making code infinite times faster.
        
       | adzm wrote:
       | i have never before thought that a function could 'fall through'
       | to another function. why does this behavior even exist?
        
         | kzrdude wrote:
         | Well you leave the C++ realm (execution model), as you should
         | with UB and it depends on implementation. The implementation of
         | the compiler was such that the two functions are placed after
         | each other in the machine code; and if the first function
         | doesn't return, then you continue executing into the code for
         | the next function.
        
           | Someone wrote:
           | But the compiler assumes the function will make forward
           | progress. If the function does that, it will return, so why
           | doesn't the compiler emit a function epilogue?
        
             | muvlon wrote:
             | The compiler can assume that the function will return, but
             | it can also statically deduce that the function cannot
             | return. That's a contradiction, so the compiler deduces
             | that the function is simply UB when called, i.e. no need to
             | emit an epilogue. It's the logical principle of explosion
             | in compiler format, basically.
        
             | gizmo686 wrote:
             | Because there is an infinite loop that makes the epilogue
             | unreachable, so it is safe for the compiler to remove it!
             | 
             | Sure, that optimization interacts badly with the
             | optimization that removes the infinite loop. But half the
             | point of UB is to avoid needing to deal with such
             | interactions, because they are defined out of existence.
        
           | raverbashing wrote:
           | This makes no sense to me
           | 
           | If I think about asm:
           | 
           | function1:                   (do stuff)              jp
           | function1              ret
           | 
           | function2:                   (other stuff)              ret
           | 
           | main:                   call function1              call
           | function2
           | 
           | the 2nd call _might_ happen internally due to branch
           | prediction but in practice it shouldn 't and the processor
           | fixes this
           | 
           | Oh yeah and TFA also goes with:
           | 
           | > The funny bit is that C got this right.(...) but C included
           | one more rule: loops whose controlling expression is a
           | constant expression may not be assumed to terminate.
           | 
           | Well, duh! A broken clock is right twice a day it seems
        
             | rcxdude wrote:
             | With UB the compiler has no particular requirement to emit
             | the 'ret'. (or, in the example, anything at all for the
             | function)
        
         | echoangle wrote:
         | I'm also confused that an uncalled function is even compiled
         | and linked, wouldn't it make sense to remove it entirely if the
         | compiler can detect that it's never called?
        
           | rcxdude wrote:
           | If it's declared as static, maybe (well, usually, in my
           | experience. You'll also usually get an unused warning).
           | Otherwise the compiler can't assume some other compilation
           | unit won't want it. Linkers can perform a garbage collection
           | pass but they don't often do it by default and they often
           | need finer grained information from the compiler (see the gcc
           | arguments --ffunction-sections and -Wl,--gc-sections)
        
             | lou1306 wrote:
             | I can understand adding the 'unreachable' function to the
             | object file, I can even understand plugging it into the
             | final executable, what I (and most other people) object to
             | is making it the de-facto entry point.
             | 
             | This is literally the opposite behaviour compared to what
             | is written in the source code, even when you "assume the
             | infinite loop terminates".
        
               | echoangle wrote:
               | That's the problem with UB, once you hit it (or even have
               | it in your code), you can't really trust anything about
               | the execution anymore. That the function is called isn't
               | something the compiler does on purpose, it's just that
               | the main function is compiled empty due to the UB and the
               | function directly behind it is executed because the CPU
               | just keeps looking for the next instruction.
        
               | rcxdude wrote:
               | Yeah, that's what UB does. You get to see the arbitrary
               | behaviour of the underlying machine with whatever the
               | compiler produces.
        
         | rcxdude wrote:
         | The CPU doesn't really see functions, it just sees
         | instructions. Functions are a convention on top of the machine
         | code. What happens in this case is the compiler emits
         | essentially a malformed function: it ends without performing a
         | return, so execution just continues into the next function in
         | memory. You can get the same behaviour by missing a 'return'
         | statement from a function that needs one (though in that case
         | I've also seen kind of the opposite: the function returns into
         | the function two slots up in the stack, essentially returning
         | from the function that called it! Undefined behaviour can
         | utterly destroy normal control flow).
         | 
         | Probably the process was one optimization pass saw that the
         | function will never return due to an infinite loop, and removed
         | the function return from the IR of the function, then a later
         | pass saw that the infinite loop was a no-op and undefined so
         | removed that as well, leaving a function that basically did
         | nothing, not even return.
        
           | echoangle wrote:
           | > The CPU doesn't really see functions, it just sees
           | instructions. Functions are a convention on top of the
           | machine code.
           | 
           | Not really true, most instructions set have instructions
           | specifically to implement functions as found in normal
           | programming languages. x86 has CALL and RET for example.
           | 
           | https://en.wikipedia.org/wiki/X86_calling_conventions
           | 
           | Of course the compiler can stil optimize by inlining etc.,
           | but functions still mostly exist at the assembly level.
        
             | rcxdude wrote:
             | they have instructions for implementing them, but the
             | important point here is that functions are still only
             | defined by instructions that are executing between a call
             | and ret instruction (or their equivalent more spelled-out
             | equivalent operations), and not only can these not match up
             | with what the compiler considers a function (for useful
             | reasons like tail-calls as well as not-useful reasons like
             | compiler bugs and UB), it might not be statically obvious
             | exactly what instructions these are. So the CPU in practice
             | has only a rough guess of where the function boundaries are
             | (it might use these guesses for things like branch
             | prediction, but they don't define the visible execution of
             | the code beyond the nuts and bolts of what those
             | instructions actually do).
        
         | apple1417 wrote:
         | The assembly gives a bit of a hint as to what's happening.
         | main:                  unreachable():                 push
         | rbx                 ...
         | 
         | Due to the undefined behavior, it decides calling main must be
         | impossible, so the easiest thing to do is just give up, don't
         | bother defining the rest of it. You can also do the same with
         | std::unreachable(). But the label for the function still sticks
         | around for some reason, so when you jump to it, it falls
         | through. Which leads to the really stupid fact that reordering
         | the functions changes the behavior.
         | 
         | I assume there are good reasons they can't just completely
         | delete the label. Maybe it would screw linking, or with cases
         | where you deliberately have multiple labels for the same
         | function. And if the effect is only visible due to undefined
         | behavior, it's not _technically_ wrong. But I have always
         | thought this is such a stupid case, surely it can 't be that
         | complex to add a trap instruction, even in an optimized build
         | you shouldn't really care if it slows down a function that's
         | "never called".
        
           | rcxdude wrote:
           | I suspect it's more a chain of: emitting the ret is
           | unnecessary because the infinite loop will never return ->
           | emitting the infinite loop is unnecessary because there's no
           | side effects within it and it's undefined behaviour ->
           | emitting any setup for the function is necessary because it's
           | doing nothing else (all probably decisions from different
           | stages of the compiler).
        
       | adzm wrote:
       | as an aside, i've always preferred the zoidberg for (;;) to
       | while(true)
        
         | glouwbug wrote:
         | I think you're thinking of (;,,;)
        
       | oleganza wrote:
       | Why is null-terminated C string considered a "billion dollar
       | mistake", but UB isn't?
        
         | Guvante wrote:
         | Null terminated strings were an intentional compromise, known
         | to be inferior for execution but superior for memory
         | 
         | Null being an "allowed" value for pointers is the mistake e.g.
         | what became nullptr. "Allowed" because garbage values are
         | garbage.
        
           | account42 wrote:
           | Think of the alternative where we'd be dealing with endless
           | issues because someone though 255 or 2^16-1 characters ought
           | to be enough for everyone.
        
             | cppcppcpp wrote:
             | VB6 :)
        
         | DonaldPShimoda wrote:
         | The "billion-dollar mistake" was about implicitly nullable
         | values, i.e., allowing a variable with type `T` to also be set
         | to `null`, not null-terminated strings.
         | 
         | Anyway, one argument is that UB is fundamentally useful in
         | languages that are insufficiently type-safe, like C and C++.
         | The "holes" in the specification allow for regions where the
         | compiler can optimize the code in ways you may not expect.
         | 
         | As we have developed more advanced type systems, the utility of
         | undefined behavior has lessened considerably.
        
           | returningfory2 wrote:
           | Agreed that this is why a lot of people support the current
           | UB situation, but the history of UB makes this feel wrong:
           | 
           | > As far as I can tell, C89 did not use performance as a
           | justification for any of its undefined behaviors. They were
           | non-portabilities, like signed overflow and null pointer
           | dereferences, or they were outright bugs, like use-after-
           | free. But now experts like Chris Lattner and Hans Boehm point
           | to optimization potential, not portability, as justification
           | for undefined behaviors. I conclude that the rationales
           | really have shifted from the mid-1980s to today: an idea that
           | meant to capture non-portability has been preserved for
           | performance, trumping concerns like correctness and
           | debuggability.
           | 
           | https://research.swtch.com/ub
        
             | DonaldPShimoda wrote:
             | Oh undoubtedly; I didn't mean to imply that performance was
             | the reason behind the _origin_ of UB, though I can see how
             | my comment would read that way. Thank you for adding the
             | note!
        
         | nicoburns wrote:
         | Probably because null-terminated strings are completely
         | avoidable, whereas some amount of UB is all but required for
         | performance (albeit C and C++ have far too much).
        
         | ameliaquining wrote:
         | I recommend this explanation of why UB is good and necessary
         | (but C and C++ are doing it wrong, defining some things as UB
         | that really shouldn't be):
         | https://www.ralfj.de/blog/2021/11/18/ub-good-idea.html
        
         | fooker wrote:
         | This comes from a fundamental misunderstanding of what UB is.
         | 
         | Think of this piece of code - `y * x / y`.
         | 
         | Would you like to simplify it to just `x` ?
         | 
         | You need to either lean on UB to do so or have some magical way
         | to prove that y can not be 0.
         | 
         | Otherwise this transformation changes behavior, and is illegal.
        
           | colejohnson66 wrote:
           | Why not just execute what I wrote? Maybe I'm doing some
           | rounding?
        
             | layer8 wrote:
             | Because we want compilers to perform the optimizations, not
             | programmers.
        
             | fooker wrote:
             | Because we want people to write simple idiomatic code that
             | runs fast today and also in ten years on alien hardware.
             | 
             | > Maybe I'm doing some rounding?
             | 
             | Compilers won't do this optimization when it is illegal to.
             | If you disagree with the compiler's idea of what is legal,
             | you can either write inline assembly, or put this code into
             | an always inlined, but never optimized function.
        
       | account42 wrote:
       | Unfortunate. There isn't ever a good reason to have an infinite
       | loop so concerned compilers could have just diagnosed this as a
       | warning.
        
         | echoangle wrote:
         | The article mentions a use case for that:
         | 
         | > What I found is that this is common in embedded and kernel
         | code as a halt-on-error pattern. When a fatal error occurs and
         | there's no operating system to exit to, you simply stop:
        
           | account42 wrote:
           | Low level code can and should use assembly to get the precise
           | effect they desire in these cases.
        
             | mdspan wrote:
             | That would be pretty cumbersome though. If you're targeting
             | N different architectures, you would have to write N
             | different assembly blocks.
        
             | rcxdude wrote:
             | I shouldn't need to drop to assembly to get an infinite
             | loop that works!
        
             | echoangle wrote:
             | Why not just allow infinite loops instead of having me
             | write assembly for it though?
        
               | account42 wrote:
               | Because a compiler being allowed to assume that a loop
               | always terminates gives it more room to optimize the 99%
               | of loops that aren't supposed to run until the heat death
               | of the universe.
        
               | echoangle wrote:
               | Or you could just detect while loops with constant
               | condition (like the C standard) and not touch any
               | programs that don't exhibit UB while allowing infinite
               | loops for other use cases at zero runtime cost and
               | negligible compile cost.
        
           | pdonis wrote:
           | If this is a genuine use case, I wonder why the language
           | can't just introduce a built-in function for it. For example,
           | std::get_stuck_here(). Then the compiler would know not to
           | optimize this away. The implementation under the hood could
           | still be an infinite loop, but the compiler would not have to
           | guess why it's there.
        
             | sigbottle wrote:
             | __asm__ __volatile("hlt"); when doing quick and hacky
             | debugging could work
        
             | gpderetta wrote:
             | one could already add loads off a volatile and portably
             | prevent the loop from being optimized. But there was
             | already a lot of existing embedded code that had this sort
             | of loop, (and more will be written as it is an existing
             | idiom) which the committee wanted to un-break.
        
         | weinzierl wrote:
         | For Rust the infinite loop is important enough to have its own
         | keyword.
        
           | kibwen wrote:
           | The reason for this is interesting. Loop constructs that
           | you're guaranteed to enter have implications for control flow
           | (in every language, not just Rust). It means that the
           | following program is valid in Rust:                   let x;
           | // declared, but uninitialized variable         loop { //
           | control flow is guaranteed to enter this loop             if
           | some_condition() {                 x = 42; // initialize x
           | break;             }         }         foo(x); // Rust knows
           | that x is initialized as of here in all possible paths
           | 
           | In contrast, while loops check their condition before
           | entering, which means the entire loop body might be skipped.
           | Languages which guarantee initialization-before-use might
           | special-case certain conditions for while loops as a hint to
           | the control flow analysis (e.g. Java special-cases
           | `while(true)`), but obviously this doesn't generalize to
           | arbitrary conditions.
           | 
           | Interestingly, this all suggest that, in C-like languages,
           | the more natural implementation of an infinite loop should
           | not be `while(true)` nor `for(;;)`, but rather `do {}
           | while(true)`, because do-while are also guaranteed to enter
           | their body (and note that Rust doesn't feature do-while
           | loops).
        
         | sumtechguy wrote:
         | > There isn't ever a good reason to have an infinite loop
         | 
         | That seems to be a very broad statement. For example in a
         | system where interrupts mostly control things this sort of 'do
         | not close the program' could be useful.
         | 
         | A guy I worked with had one I never would think of because I do
         | not work in that field.
         | 
         | But yeah a warning would probably be useful.
        
         | not_the_fda wrote:
         | Interrupt driven super loops are very common on bare metal
         | systems.
        
         | mdspan wrote:
         | Compilers can still diagnose something as a warning even if
         | it's not UB.
        
           | cppcppcpp wrote:
           | Can, yes. Must, no.
        
       | aabolfazl wrote:
       | Sometimes while (true){} doesn't mean anything clever. It just
       | means the system is broken stay here.
        
       | JoshTriplett wrote:
       | > When both conditions are met, the loop body is replaced with a
       | call to std::this_thread::yield().
       | 
       | Insert screaming here.
       | 
       | An infinite loop, with no library calls whatsoever, gets a
       | _system call_ inserted. That 's a horrible surprise waiting to
       | happen.
       | 
       | The entire concept of the "forward progress guarantee" is broken.
       | An infinite loop should compile to an infinite loop. Nothing
       | more, nothing less.
        
         | rcxdude wrote:
         | Yeah, this is almost the worst way they could choose to 'fix'
         | the problem.
        
         | ameliaquining wrote:
         | I'm curious, what exactly do you imagine going wrong here?
        
           | rcxdude wrote:
           | The biggest headache will probably be it getting emitted in
           | inappropriate contexts: where there is no actual means to
           | sched_yield for whatever reason (bare metal, kernel,
           | whatever). The second is just that the behaviour of the
           | infinite loop changes: suddenly you're getting a bunch of
           | extra system calls from your spinning thread instead of just
           | a high CPU usage, which could disguise the issue or perhaps
           | cause problems for other parts of the system. I don't see a
           | good reason for the transformation: pretty much any time you
           | are writing a bare infinite loop like this you don't want
           | anything else to happen (it's also silly that it only happens
           | with a particular spelling of an infinite loop, keeping the
           | others still undefined).
        
             | JoshTriplett wrote:
             | "Emitted in inappropriate contexts" is very much one of the
             | shapes I would expect unpleasant surprises to take, yeah.
             | If you're writing code in C, you often need a lot of
             | control over exactly what's happening. You might, for
             | instance, be writing a .so for use with LD_PRELOAD, where
             | it's important that you know everything being called so you
             | can't accidentally recurse. You might be writing code for a
             | sandbox, where you have an allowlist of permitted syscalls.
        
               | pjmlp wrote:
               | Exact control is only available in Assembly, minus
               | unavoidable hardware flaws, everything else even a minor
               | compiler update might change the outcome of the code.
        
             | criddell wrote:
             | > suddenly you're getting a bunch of extra system calls
             | from your spinning thread instead of just a high CPU usage
             | 
             | Isn't the point that the loop was undefined behavior and so
             | the spinning thread might not actually be spinning to begin
             | with? It could be doing anything and sometimes did stuff
             | like run the next block of code.
             | 
             | If you really want an infinite loop that does nothing (not
             | sure why), you can do that now on any standards conforming
             | compiler with some of the methods Sandor described.
        
               | rcxdude wrote:
               | It being undefined behaviour before doesn't make all
               | possible definitions of that behaviour equally
               | reasonable. The strangest thing to me is that I don't
               | know who this behaviour definition _is for_. Infinite
               | loops like this are a pattern that 's almost entirely
               | mutually exclusive with situations where a scheduler is
               | relevant.
               | 
               | I'm not too concerned about it being possible to make a
               | loop at all (there's a lot of ways to add a 'side-effect'
               | that will probably result in the same assembly), I'm
               | concerned with a) the strange unwillingness to just
               | define a sensible behaviour in this case, especially when
               | C already has one (and GCC already in practice implements
               | a slightly different but also perfectly reasonable
               | interpretation, both of which work for all the normal
               | ways someone might write such a loop), and b) the huge
               | amount of existing code which uses this construct because
               | for the most part compilers did not actually cause
               | problems with it.
        
             | nicebyte wrote:
             | arguably there are already several places in the language
             | where things like this can happen. for example,
             | initializing a static function variable has certain thread
             | safety guarantees (two threads entering the function won't
             | step on each other), and while it's nice to not worry about
             | it, this can certainly be a problem if you're trying to
             | stay close to the metal and not pull in any dependencies.
             | 
             | > I don't see a good reason for the transformation: pretty
             | much any time you are writing a bare infinite loop like
             | this you don't want anything else to happen (it's also
             | silly that it only happens with a particular spelling of an
             | infinite loop, keeping the others still undefined).
             | 
             | I'm not disagreeing with you, but two things worth
             | considering are 1) you don't always write loops like that
             | _intentionally_; 2) if a bug like that slips into
             | production system, it would be good to make sure it doesn't
             | starve other threads.
        
               | rcxdude wrote:
               | This is true. Static initialization will often generate
               | calls to lock functions and that can be a faff to deal
               | with. But I don't see what the point of the sched_yield()
               | is. Using it at all is already a code smell and calling
               | it repeatedly in a tight loop is the kind of thing kernel
               | developers were trying to beat out of application
               | developers decades ago because it just isn't really very
               | helpful (and often actively harmful) with any but the
               | dumbest of schedulers. It's certainly not very useful for
               | avoiding thread starvation.
        
             | mitxela wrote:
             | Impl specific. If you're building bare metal, pass
             | -ffreestanding so GCC knows it's not allowed to call OS
             | functions.
        
           | rfgplk wrote:
           | The language is already littered with these "the compiler
           | shall insert" and then a reference to the STANDARD LIBRARY
           | FEATURE N.X. Which means if you're compiling in a
           | freestanding environment half the time you'll get linker
           | errors such as "couldn't find symbol whatever". And what's
           | worse the compiler inserts a call to a function that is
           | LITERALLY STD NAMESPACED. Meaning you have to provide that
           | signature yourself. See how std vector is hardcoded into
           | compare/meta and I can't remember what else.
           | 
           | This then forces developers to create undefined behaviour
           | because according to the standard you can't namespace std
           | your own functions even though it's required to get it to
           | work.
        
             | ameliaquining wrote:
             | Freestanding environments specifically don't have to do
             | this, they get a carve-out. (I dunno if this applies
             | elsewhere though.)
        
             | mitxela wrote:
             | Sounds like a compiler bug that a standard feature
             | implementable in freestanding doesn't work in freestanding.
        
           | yk wrote:
           | I would expect an infinite loop                   while(true)
           | std::this_thread::yield();
           | 
           | to be designed to play nice with the scheduler, while I would
           | assume a infinite loop                   while(true);
           | 
           | to not play nice with the scheduler. Now, I can't really
           | imagine where this matters except for horrible hacky attempts
           | at faking a real time scheduler on windows, but breaking
           | horrible hacky attempts at faking a real time scheduler
           | sounds like the kind of bug you hear about in the evening
           | news.
        
             | nicebyte wrote:
             | under what conditions would the infinite loop be scheduled
             | over something else after it has run out of its time slice?
        
             | rcxdude wrote:
             | I would expect them to be the same or for the former to be
             | worse. It's rarely useful to call sched_yield at all, but
             | calling it repeatedly in a loop seems more likely to expose
             | bad behavior in a scheduler than improve the interaction.
             | Schedulers are already perfectly well designed to handle
             | threads trying to take up 100% of the CPU: that's the
             | default state for any CPU-bound task.
        
           | 112233 wrote:
           | I would expect quite a number of embedded use cases to
           | suddenly break. Yay, free CVEs!
        
         | ozgrakkurt wrote:
         | But the compilers have to optimize the crap code in big tech
         | codebases by 0.5%, it saves a lot of money.
         | 
         | Also performance doesn't matter that much and developer time is
         | more important btw, keep using react.
        
           | muvlon wrote:
           | It's not even about optimizing some big tech codebase by
           | 0.5%. The progress guarantees in particular are in place s.t.
           | Nvidia can choose a certain implementation strategy in Cuda
           | C++ that has "surprising" consequences for users (one thread
           | getting stuck in an infinite loop that never yields can
           | livelock its entire warp) but still get to claim "full C++
           | standards compliance".
        
             | JoshTriplett wrote:
             | So _let it livelock the entire warp_ when someone writes an
             | infinite loop. Should we start replacing integer division
             | by zero with INT_MAX so that people aren 't "surprised" by
             | their program crashing?
        
               | muvlon wrote:
               | I mean that's what they did, and that's why there's that
               | UB. All I'm saying is that this is the "weird platform
               | behaviors exist and must be legalized by the standard"
               | kind of UB and not the "we want a 0.5% win for
               | benchmaxxing" kind of UB (the standard has plenty of
               | both).
        
               | fc417fc802 wrote:
               | No, they didn't. UB is a cop out and inserting yield is
               | just plain bad. Locking up one or more threads in an
               | implementation defined manner would be the outcome of
               | least surprise (I already know it's going to lock up _at
               | least_ the one thread).
        
               | mitxela wrote:
               | The standards intended interpretation of UB was always
               | intended to be something like "implementation defined, no
               | documentation required" to allow for implementation
               | weirdness, even unpredictable ones. It was compiler
               | authors who decided do abuse this allowance to do really
               | unintuitive things instead of weird platform weirdness.
        
               | IcyWindows wrote:
               | 100% There are implementations that have sane behavior
               | for "UB" instead of making it an excuse to misbehave.
               | 
               | The ISO standard is not the same as a language from a
               | single vendor.
        
               | mitxela wrote:
               | IIRC a lot of it was benchmark gaming between GCC and
               | LLVM.
        
               | cryptonector wrote:
               | Because one can _bleeping_ see that that 's what would
               | happen. Locking up a thread isn't a good thing, but it's
               | a lot better than UB. There was never a need to make this
               | UB.
        
             | mitxela wrote:
             | Isn't that the strategy for most of the stuff in C++? It's
             | the common denominator of a wide variety of platforms.
             | That's why numbers didn't have to be two's complement and
             | characters didn't have to be ASCII for ages.
        
             | mschuetz wrote:
             | I don't see the issue. Just let wrong code do wrong things
             | But let it do the expected wrong thing, rather than
             | changing the code to something unexpected.
        
               | cryptonector wrote:
               | "No." <-- the C++ committee.
        
           | vrighter wrote:
           | performance doesn't matter.... so datacenters would be
           | equally happy running software that runs half as fast but
           | uses 10% more power?
        
         | ibobev wrote:
         | > An infinite loop should compile to an infinite loop.
         | 
         | I think that a compiler option should control this. It can be a
         | nice optimization, but the programmer should be able to opt
         | out.
        
           | Xirdus wrote:
           | The programmer can opt out by terminating the loop.
        
           | WalterBright wrote:
           | Every flag that changes the semantics of the code bifurcates
           | the language into two languages.
        
             | ibobev wrote:
             | If we accept this definition, C++ already seems to be many
             | different languages. At my job, I'm currently fighting
             | floating-point determinism issues across different build
             | configurations, compilers, CPUs, operating systems, and
             | standard library and libm implementations so that snapshot
             | tests pass with the same hashes on all platforms. I can
             | confirm that this is a complete nightmare.
        
               | WalterBright wrote:
               | Yup. I tried hard to not allow D's behaviors to be
               | changed based on a compiler switch. Yes, we have switches
               | to enable certain features, but not silent behavior
               | changes.
               | 
               | It's not perfect, but the forest of such switches in C
               | compilers motivated D to not have them.
        
             | cryptonector wrote:
             | Alternatively every flag that changes the semantics of the
             | code is a workaround for either legacy code no one will fix
             | or language committee decisions that have unintended side
             | effects.
        
               | WalterBright wrote:
               | Are C/C++ chars signed or unsigned? What a mess!
        
         | saghm wrote:
         | I guess given that it was UB before, the compiler was already
         | allowed to put a system call here if it wanted for some reason
        
           | fc417fc802 wrote:
           | Yes, it was a horrible situation that has been replaced by an
           | only slightly less horrible situation.
        
             | saghm wrote:
             | I don't totally agree with this. To me, UB is an order of
             | magnitude worse than pretty much anything else, so this is
             | more than "slightly less" horrible. I don't necessarily
             | disagree that this is still horrible, but I also don't
             | write an C++, so I'm mostly just commenting as an outside
             | observer.
        
           | chowells wrote:
           | Not only that, the code was _wrong_. The specification is
           | quite clear that correct programs don 't cause UB to be
           | executed at run time. If your wrong code now produces wrong
           | results, that's because it's wrong. That your compiler
           | allowed you to get away with it for decades is a compiler
           | bug, not a feature.
           | 
           | Do I fully believe all of the above? Not exactly. But
           | compiler authors do. Does it make a really good argument to
           | never use C or C++? Yes. If only we had 50 years of
           | optimization work in any language with better semantics.
        
             | saghm wrote:
             | Yeah, my slightly more verbose take is that a language that
             | requires you to not ever make any mistakes in order to have
             | a program behave in a predictable way is not a particularly
             | good choice of language if you the ability to pick
             | something else.
        
             | JoshTriplett wrote:
             | The mistake was declaring infinite loops to be UB in the
             | first place.
        
             | throwaway786678 wrote:
             | _> That your compiler allowed you to get away with it for
             | decades is a compiler bug_
             | 
             | That UB was added in C++11.
        
           | cryptonector wrote:
           | Yes, but now it has to. I guess that's better? than UB,
           | maybe.
        
         | marcosdumay wrote:
         | I don't understand your problem. Did you expect your C++
         | program to get uninterrupted access to the computer? What
         | progression do you think isn't happening there?
         | 
         | I think you are misinterpreting that. That phrase unambiguously
         | says the loop is preserved on the final binary.
        
           | JoshTriplett wrote:
           | I expect an infinite loop to be compiled into, for instance,
           | a jump instruction jumping to itself. The OS, _if there is
           | any_ , is welcome to interrupt and context switch. I don't
           | expect code that has _no function calls at all_ to have a
           | _system call_ inserted into it.
        
             | marcosdumay wrote:
             | Ok, I get this.
             | 
             | The problem is that what you want is completely against the
             | spirit of the entire language.
             | 
             | If your point is that C++ should be more like C in general,
             | I can agree with that. But if your point is that C++ should
             | be literal on this specific case, performance be damned,
             | and the rest of it is ok, then no, that's a bad one.
        
               | JoshTriplett wrote:
               | I was utterly unconvinced that the original infinite-loop
               | UB gave the compiler any important performance
               | optimization, and I'm unconvinced that _this_ is
               | providing useful value to compensate for its surprise. If
               | I wanted a yield in my infinite loop, I 'd add one.
        
               | mitxela wrote:
               | The original UB was to allow the compiler to merge two
               | loops without proving termination.
        
               | CamperBob2 wrote:
               | What difference does it make? If the loop doesn't
               | terminate, it doesn't terminate, which is almost always a
               | bug, except when it's not. If it _does_ terminate, then
               | great, it terminates.
               | 
               | Merging a buggy loop with another loop creates... a buggy
               | loop.
        
               | teo_zero wrote:
               | Take this example:                 for (i=0;i<n;i++)
               | A[i]=0;       for (i=0;i<n;i++)         B[i]=0;
               | 
               | It can be conveniently transformed into this:
               | for (i=0;i<n;i++)         A[i]=B[i]=0;
               | 
               | They are exactly equivalent _except_ if the first loop
               | never terminates.
               | 
               | Now, the compiler could try to understand if the first
               | loop does or doesn't terminate, and apply or not the
               | optimization accordingly, but Turing tought us that is
               | indeed a hard task!
               | 
               | Or it could decide to never apply it, for fear of those
               | rare and usually pathological cases where the first loop
               | doesn't terminate.
               | 
               | Or it could decide to apply it by default and accept that
               | in those cases the program does something different than
               | what the source code says. The latter is better known as
               | UB.
               | 
               | The third option won, and that's why infinite loops are
               | UB in the standard.
        
             | leni536 wrote:
             | A call to a standard library function is still subject to
             | the as if rule. It doesn't have to manifest into a call
             | instruction to a standard library function. Much like
             | memcpy in source code doesn't have to manifest to a call
             | instruction.
        
               | fc417fc802 wrote:
               | Me: I don't expect to be stabbed.
               | 
               | You: But you only _might_ be stabbed. It isn 't
               | _required_ to happen only _permitted_.
        
               | ack_complete wrote:
               | Yes, but the compiler does have to preserve any
               | observable behavior produced by the call to the standard
               | library function. Being able to omit this inserted
               | yield() by the as if rule would mean that it isn't
               | observable, which would also mean that the compiler could
               | already add or not add it anywhere as needed without
               | changing the behavior of the program. Which would
               | seemingly make the inserted yield() pointless as it would
               | have no effect.
        
           | rcxdude wrote:
           | Any program in an OS only gets as much resources allocated to
           | it as the OS allows (OK, in any general-purpose OS written in
           | the past few decades). sched_yield() doesn't actually reduce
           | that allocation in most cases, anyhow: in fact it has a
           | higher chance of increasing the resources that the thread
           | uses spinning in a loop because it's gonna be thrashing the
           | scheduler as well.
        
         | usefulcat wrote:
         | Yeah, I don't get it either. Like if I wanted to call
         | std::thread::yield() inside an infinite loop, I could, you
         | know, just do that myself?
         | 
         | An obvious question (that TFA does not address) is, why is the
         | forward-progress guarantee needed? Since that is the ostensible
         | justification for this new invisible behavior.
        
         | UncleMeat wrote:
         | Forward progress guarantee is what allows for conversion
         | between recursion and iteration for performance optimization.
         | Otherwise these have different characteristics (recursion blows
         | the stack, a loop hangs).
        
         | amluto wrote:
         | I grilled an LLM for a bit to see if it could justify the old
         | forward progress rule. The only thing I got that passed the
         | smell test was that it's useful for the optimizer to be able to
         | optimize:                   messy_pure_computation();
         | some_atomic.store(1, relaxed);
         | 
         | by moving the store before the computation. (Stronger stores
         | would require additional analysis.)
         | 
         | I admit I'm unconvinced that this is particularly useful.
         | 
         | (I got many other ideas that did not pass my personal smell
         | test.)
        
           | raphlinus wrote:
           | You will find the answer you seek not from an LLM, but from
           | the talk Forward Progress Guarantees in C++ by Olivier Giroux
           | at CppNow 2023. It's a long talk, with lots of details about
           | forward progress, but I've set the timestamp[1] to the
           | infinite loop bit.
           | 
           | [1]: https://youtu.be/g9Rgu6YEuqY?si=_l9JwKhjvIdFEDEX&t=3819
        
           | mitxela wrote:
           | I thought it was generally so the compiler can merge two
           | computation loops without proving if one of them runs forever
           | .
        
             | murderfs wrote:
             | Correct. See N1528: "Why undefined behavior for infinite
             | loops?" https://www.open-
             | std.org/jtc1/sc22/wg14/www/docs/n1528.htm
        
       | ameliaquining wrote:
       | The article, most unfortunately, doesn't explain why anyone would
       | want infinite loops to be UB in the first place. I found this
       | explanation: https://www.open-
       | std.org/jtc1/sc22/wg14/www/docs/n1528.htm
        
         | omoikane wrote:
         | The article mentions it's a halt-on-error pattern:
         | 
         | https://www.sandordargo.com/blog/2026/09/16/cpp26-trivial-in...
         | 
         | Edit: sorry, missed the UB bit.
        
           | rcxdude wrote:
           | That's more why you would want them to be defined in the
           | first place.
        
           | Lvl999Noob wrote:
           | That says why they _don 't_ want it to be UB. The question, I
           | believe, was why they want statically-known-infinite non-
           | trivial loops to continue being UB.
        
           | pseudohadamard wrote:
           | One minor nit, it's not halt-on-error, it's restart-on-error
           | since the watchdog then restarts the system. The technical
           | term for this is rejuvenation and it's standard practice in
           | SCADA and similar to deal with this-shouldn't-happen error
           | conditions.
        
         | layer8 wrote:
         | See https://news.ycombinator.com/item?id=49760653.
        
       | omoikane wrote:
       | > The loop must be a trivially empty iteration statement --
       | meaning its body is literally empty
       | 
       | This seems to say that the loop body can not be "continue".
       | Indeed, I just tried -std=c++26 with ";" and got an infinite loop
       | as promised, but "continue" restores the undefined behavior:
       | 
       | - "while(true);" -> https://godbolt.org/z/T65o51crx
       | 
       | - "while(true) continue;" -> https://godbolt.org/z/Pj9raEcnP
       | 
       | This is unfortunate since I know of one style guide that prefers
       | "continue" over single semicolons. I guess all those code will be
       | doing "while(true) {}" from now on.
       | 
       | https://google.github.io/styleguide/cppguide.html#Formatting...
        
         | nullc wrote:
         | Nice underhanded coding technique / bugdoor material once the
         | "infinite loops are not UB" notion is widespread enough. :P
        
       | wahern wrote:
       | > When both conditions are met, the loop body is replaced with a
       | call to std::this_thread::yield(). This gives execution of the
       | loop the forward-progress semantics it previously lacked.
       | 
       | That's the epitome of the hidden code downside that Linus and
       | many others dislike about C++. For constructors and destructors
       | it's somewhat unavoidable and not so random, though Rust does
       | better at limiting the blast radius of non-local code, at least
       | in the drop case.
       | 
       | If they didn't want to adopt the C11 rule, the C++ committee
       | should've explored a rule that required the compiler to emit a
       | diagnostic or error for trivial loops (whether as defined by C11
       | or otherwise), requiring the programmer to explicitly insert
       | ::yield or similar. No hidden code, and less opportunity for the
       | compiler to do surprising things.
       | 
       | The C committee has been rigorously enumerating UB cases in the
       | standard and addressing each case in turn, often by requiring a
       | diagnostic, error, or by turning it into implemention defined
       | behavior. But inserting code like that would be unthinkable.
        
         | IsTom wrote:
         | > should've explored a rule that required the compiler to emit
         | a diagnostic or error for trivial loops (whether as defined by
         | C11 or otherwise), requiring the programmer to explicitly
         | insert ::yield or similar
         | 
         | It wouldn't work when this kind of loop is generated by
         | macros/templates in some unreachable case left after const
         | folding.
        
           | rcxdude wrote:
           | If it's truly unreachable then it's not likely to be a
           | problem. If it is reachable and it's emerging from some
           | macros and templates then I would be more inclined want a
           | warning for it.
        
             | IsTom wrote:
             | Yeah, but then you need compiler to somehow know if it's
             | truly unreachable to know when to emit the warning and when
             | to not do that.
        
               | krupan wrote:
               | If it's a warning and not an error then emit it whether
               | it's unreachable or not
        
         | rfgplk wrote:
         | It's catastrophic actually. Like disastrously catastrophic. It
         | started with C++20 mostly, and has only kept getting worse from
         | then. See zero initializing variables by default (WHY?)
         | compare/meta including half the STL and HARDCODING those
         | symbols, std::initializer_list being in the std namespace (if
         | you don't include <initializer_list> you literally can't use
         | it, and there is no such thing as a __initializer_list or some
         | internal symbol), the entire coroutine library where you MUST
         | provide coroutine_handle, noop_coroutine, suspends et al
         | (coroutines aren't that bad because they're not necessarily
         | spaghetti).
         | 
         | <meta> is the single WORST OFFENDER, where they hardcode
         | std::vector (literally std::vector in the std namespace)
         | std::ranges std::allocator.
        
           | aw1621107 wrote:
           | > See zero initializing variables by default
           | 
           | Strictly speaking the standard only requires some pattern
           | that is not tied to program state. Zero works for that, but
           | so do other static patterns like 0xABAB... or the like.
           | 
           | > (WHY?)
           | 
           | The motivation section of the corresponding paper [0] might
           | be interesting. tl;dr: it lets wrong code be wrong without
           | suffering from (all) the consequences of full-blown UB.
           | 
           | [0]: https://www.open-
           | std.org/jtc1/sc22/wg21/docs/papers/2024/p27...
        
             | fc417fc802 wrote:
             | In other words, it's a sane default that you can opt out of
             | on a case by case basis which is the way it should have
             | been all along.
        
             | WalterBright wrote:
             | D initializes floating point variables to NaN by default.
             | And chars to 0xFF. Yes it's controversial!
        
             | dahart wrote:
             | I'd guess the concern is performance, not what initializer
             | value is used. And performance is a valid concern that is
             | discussed in the proposal, and a reason there's an escape
             | hatch. Still, it might cause some confusion.
        
           | mitxela wrote:
           | I can't find anything saying variables are zero initialized
           | by default in C++20. But the reason to do so is obvious: many
           | bugs are caused by the lack of this, and as long as you can
           | opt out with "= void" or something, it's not violating C++
           | core principles.
        
             | dahart wrote:
             | They were saying the problematic philosophy started in C++
             | 20, not the variable initialization rule.
             | 
             | Yes the reason is obvious, but it's neither simple nor
             | black and white. One huge problem is that this can cause
             | serious performance regressions, and you have to change
             | your code to opt out, e.g. add "[[indeterminate]]". There
             | are many, many cases in high performance computing where
             | the intended & desired behavior is don't touch my variables
             | until I fill them.
             | 
             | This is _changing_ C++ core principles, there's a new
             | designation for the state of a variable: erroneous. It's
             | also subtle and weird, because you can still have well-
             | defined behavior even with erroneous state. It does seem
             | like this might be an experiment though, I don't think this
             | is the end of the story. (It seems they're already talking
             | some redesign of this idea.)
        
               | ack_complete wrote:
               | What I'm most annoyed at with the variable initialization
               | change is that:                 - It's potentially a
               | performance change in every single function, especially
               | ones that have sizable fixed-size buffers       - If you
               | have regressions you have to spray [[indeterminate]]
               | everywhere, because there is no coarser way of
               | suppressing it.       - While the language says
               | unrecognized attributes are ignored, compilers frequently
               | warn on unrecognized attributes. Clang, for instance,
               | currently warns on [[indeterminate]].       - There is no
               | defined macro name for backwards compatibility.
               | 
               | Which means that libraries are going have to all declare
               | their own macros for [[indeterminate]] and pepper their
               | code with it.
        
               | mitxela wrote:
               | Uninitialized variables were already UB to read, because
               | some architectures have trap representations, even for
               | integers. Every register on Itanium has one.
        
               | cryptonector wrote:
               | That's not reason enough to have the compiler initialize.
        
               | zrm wrote:
               | That's assuming you were reading it without writing to
               | it. There are three common cases when that isn't true.
               | 
               | The first is that you have a fixed buffer large enough
               | for the maximum message size even though the typical ones
               | aren't that big. You most often write 1% of the buffer
               | and read it back, the other 99% is never accessed.
               | 
               | The second is that you always write the entire contents
               | before reading it but the compiler may not be able to see
               | that.
               | 
               | And the third is that you have a code path where that
               | variable is simply not used.
               | 
               | You would then have the compiler emitting instructions to
               | write zeros that are either overwritten before being read
               | or are never read at all.
               | 
               | Moreover, zero initializing the data doesn't actually
               | remove the bugs when that isn't the case. Consider the
               | first case when you mess up. You have a fixed buffer used
               | to store variable length messages. For the first message
               | the buffer is now zeros instead of uninitialized, but for
               | every subsequent message the remainder of the buffer
               | still contains the remainder of the previous message and
               | subjects you to information disclosure or data
               | modification if you're reading back a different amount
               | than was written in the associated call.
               | 
               | Now consider the second or third case. You
               | unintentionally read from a variable before assigning to
               | it. You get zeros instead of uninitialized memory, but if
               | you weren't expecting zeros, well, the UID field is now
               | 0.
        
             | nullc wrote:
             | Zero initializing also hides bugs.
             | 
             | Say you have some code that should not be reading the
             | initial state and is buggy if it does. Without zero-init,
             | valgrind and msan will give you an immediate and false
             | positive message that your code is wrong-- or forget
             | dynamic analysis: the compiler can often statically tell
             | you that the code will use an uninitialized variable. Zero
             | initialize it and you lose that signal.
        
         | pbalau wrote:
         | Is it hidden if it's explained in the standard?
         | 
         | I think Linus's complain was before there was a c++ standard.
         | An updated version of the complaint would be "this shit is
         | doing too much".
        
           | andrepd wrote:
           | An empty loop, under some non-obvious conditions, on some
           | compiler flags but not others, silently transforms into a
           | system call. In a systems programming language.
        
             | WalterBright wrote:
             | I try to minimize use of destructors for the same reason.
        
               | andrepd wrote:
               | Destructors run predictably at list, and are pervasive
               | everywhere. You know that when you exit a scope, be that
               | a function or whatever it may, the destructors of
               | variables in that scope are called. That is clear and
               | consistent. The transformation mentioned above is not.
        
               | WalterBright wrote:
               | Understanding the code requires understanding the
               | destructors of the objects you're using. Since they are
               | invisibly inserted, they are a source difficulty in
               | entirely understanding the code.
        
               | rcxdude wrote:
               | I do wonder if any of the language servers that insert
               | implied type annotations would ever also show things like
               | destructor calls in a similar manner. It seems like it
               | would be quite useful.
        
               | otabdeveloper4 wrote:
               | > Since they are invisibly inserted
               | 
               | They're not, all destructors are explicit. Seems like a
               | skill issue on your end.
        
               | WalterBright wrote:
               | Since AFAIK I'm still the only person to write a correct
               | C++ (C++98) compiler from preprocessor to object file, I
               | know all about destructors.
               | 
               | Here's a fun one for your amusement:
               | foo(a, b, c);
               | 
               | The parameters are pass by value. a, b and c are objects
               | that have destructors. Have a look at the code generated
               | for that.
               | 
               | It is nice that the compiler does the dirty work for you,
               | but the various paths with exceptions and recovery with
               | invisible code may not be well tested.
        
           | aw1621107 wrote:
           | > Is it hidden if it's explained in the standard?
           | 
           | In the context of that particular complaint, yes. From what I
           | understand the gist of it is basically that you should be
           | able to tell what is going on by looking at the code
           | _locally_ (i.e., the code is  "explicit").
           | 
           | > I think Linus's complain was before there was a c++
           | standard.
           | 
           | These emails [0]? IIRC those are the most well-known ones and
           | they are from the mid-2000s
           | 
           | [0]: https://harmful.cat-v.org/software/c++/linus
        
         | pwdisswordfishq wrote:
         | GNU C does the same: memory copies can be optimized into
         | memcpy, various operations can be realized as calls into
         | libgcc, etc.
        
           | slaymaker1907 wrote:
           | And memcpy is kind of special to C/C++ compilers. Sure, it
           | exists as a function, but it will often have special purpose
           | code generated for that particular location.
           | 
           | It's obvious why you want to inline memcpy, but the
           | specialization is more interesting. For example, I've seen
           | the compiler optimize a memcpy with a static number of bytes
           | and then use SIMD registers to do the copying with no loop at
           | all. It can even be smart enough to take advantage of memory
           | alignment for this.
        
           | rcxdude wrote:
           | Those do for the most part correspond to operations which
           | make sense in an embedded context, though.
        
         | kevin_thibedeau wrote:
         | The C++ committee has a habit of thumbing its nose at standard
         | practice. They intentionally broke bitwise operators on
         | volatiles because they wanted to be impose their atomic
         | religion everywhere. Then they had to walk that back after they
         | broke every embedded library directly manipulating hardware
         | registers.
         | 
         | Empty infinite loops are also commonplace in embedded C once
         | main is done with init and within exception handlers. They
         | don't care about anything beyond their narrow systems
         | programming worldview.
        
           | Sniffnoy wrote:
           | > They intentionally broke bitwise operators on volatiles
           | because they wanted to be impose their atomic religion
           | everywhere.
           | 
           | Could you elaborate on this?
        
             | aw1621107 wrote:
             | "broke" is arguably an overstatement. C++20 deprecated some
             | (most?) operations on volatile variables [0] in part
             | because they can misleadingly imply an atomic operation:
             | 
             | > volatile external modifications are only truly meaningful
             | for loads and stores. Other read-modify-write operations
             | imply touching the volatile object more than once per byte
             | because that's fundamentally how hardware works. Even
             | atomic instructions (remember: volatile isn't atomic) need
             | to read and write a memory location []. These RMW
             | operations are therefore misleading and should be spelled
             | out as separate read ; modify ; write, or use volatile
             | atomic operations which we discuss below.
             | 
             | This was not received particularly well in the embedded
             | community (e.g., [1]) due to said deprecation affecting
             | compound bitwise operations on volatile variables, which
             | are _extremely_ widely used to interact with hardware
             | registers. This pushback eventually resulted in C++23 un-
             | deprecating compound bitwise operators on volatile
             | variables [2].
             | 
             | [0]: https://www.open-
             | std.org/jtc1/sc22/wg21/docs/papers/2018/p11...
             | 
             | [1]: https://www.reddit.com/r/cpp/comments/jswz3z/compound_
             | assign...
             | 
             | [2]: https://www.open-
             | std.org/jtc1/sc22/wg21/docs/papers/2021/p23...
        
               | tialaramex wrote:
               | Basically on a rare occasion somebody (JF Bastien) was
               | able to land a C++ proposal which warns you if you'd do
               | something stupid, and inevitably C programmers cosplaying
               | as C++ programmers insisted "it's not stupid it's
               | advanced" and so now C++ _must not_ even warn you this is
               | a bad idea.
               | 
               | It still _is_ a bad idea, but being warned would make
               | them feel bad.
        
           | 112233 wrote:
           | By narrow luck compiler writers so far have been the sane
           | bunch, and have ignored C++ committee on many important
           | points. Thus we still have explicitly non-conformant things
           | like -fno-exceptions that lets one use C++ compiler on
           | embedded.
           | 
           | But I wonder how long that can last, with the way C++ is
           | going.
           | 
           | At one point, it will make practical sense to update codebase
           | to some other language, rather than keep fighting this one
        
             | pjmlp wrote:
             | I have been saying that C++23, or maybe C++26 due to
             | reflection, will eventually be the last standard that
             | actually matters.
             | 
             | For a large number of C++ users, it boils down to what it
             | offers beyond C, but not to the extent WG21 is driving it
             | since C++20.
             | 
             | Also the major surviving three compilers have lost wind on
             | their sails as the corporations sponsoring their
             | development have switched focus to other compiled
             | languages.
             | 
             | Other than the whole security debate, there are no features
             | that would make C++ significantly better for LLVM, GCC,
             | CLR, V8, CUDA,.. improvements.
             | 
             | In fact, some of those projects still require C++17.
             | 
             | If this sounds strange, how many care nowadays about ISO
             | Fortran 2023, or ISO COBOL 2023, despite the amount of
             | software written in them powering many busisesses, or
             | Python libraries even, e.g. SciPy.
             | 
             | Or even with C, almost 20 years later many still reach out
             | to C99, ignoring everything else.
        
               | ilayn wrote:
               | Not to take away from your points; SciPy is now Fortran-
               | free completely[0] (we are also requiring C++17 at most).
               | NumPy never had it. BLAS is all C/Assembly in all
               | optimized vendors. For LAPACK we are working on it [1].
               | 
               | Once there is enough pain, none of the talking points
               | matter for any language. They don't and can't die but
               | linger. I fear that time for C family might come in a
               | decade which would be a shame given how magical Cpp
               | compilers are, all that effort folks pouring in.
               | 
               | [0]: https://github.com/scipy/scipy/issues/18566 [1]:
               | https://github.com/ilayn/semicolon-lapack
        
               | pjmlp wrote:
               | Thanks for the update overview, and interestingly you
               | also mention C++17, as the version you currently care
               | about.
        
         | gpderetta wrote:
         | As the only observable behaviour of this_thread::yield is
         | forward progress, because of the as-if rule, the compiler
         | doesn't actually need to replace the loop, when running on a
         | runtime that guarantees preemption. That's the case when
         | std::threads are backed by kernel threads. On a M:N
         | implementation, then yes, a yield would need to be added, but
         | that would be desirable.
         | 
         | Interestingly, posix realtime FIFO scheduling doesn't preempt
         | even on kernel thread based implementations, so one reading of
         | the standard would require yield on this case. But that can
         | actually be potentially catastrophic as FIFO scheduling is
         | expected to be deterministic. But realtime scheduling is
         | already beyond the standard: I doubt gcc and clang will do the
         | transformation by default.
         | 
         | In practice the equivalence is necessary to make some obscure
         | corner of the memory model work and prevent some undesirable
         | optimizations; I expect that in practice the compilers, if they
         | implement this at all, will provide an opt-in flag, but they
         | will optimize as-if the call was there.
        
         | add2 wrote:
         | > For constructors and destructors it's somewhat unavoidable
         | and not so random, though Rust does better at limiting the
         | blast radius of non-local code, at least in the drop case.
         | 
         | Unlike C++, Rust does not manage exceptions at all; in C++, you
         | must consider situations where exceptions arise.
        
           | steveklabnik wrote:
           | If panics are set to unwind, you do need to consider it, and
           | the UnwindSafe auto trait is there to help with memory
           | safety, but logical issues can still arise.
           | 
           | It's way way more rare in Rust though.
        
         | cryptonector wrote:
         | There needs to be a way to stop this. A trivial infinite loop
         | can be useful such as for getting you into a state where you
         | can attach a debugger and examine state then have execution
         | resume elsewhere.
        
       | peterus wrote:
       | There are valid use cases for the infinite while(1) loop in
       | microcontroller programming (contrary to popular belief it
       | seems). Autogenerated HAL code for the stm32 uses it for error
       | handlers, and they support C++ so I am surprised this was UB.
       | 
       | I only use it for error handling and of course it is a bad idea
       | to use this to wait/stall in power sensitive applications, in
       | that case use wake from interrupt.
       | 
       | As an aside, I like to include a software breakpoint in my error
       | handlers. It makes debugging easier without wasting a hardware
       | breakpoint (which are physically limited by the microcontroller):
       | __BKPT();       while (1)         ;
        
         | rfgplk wrote:
         | UB according to the standard committee is "we didn't think of
         | it". It's not literal UB it's well known what it compiles down
         | to, every time. (.loop: jmp .loop)
        
           | usefulcat wrote:
           | It's not "we didn't think of it", it's literally "the
           | standard has nothing to say about it", which means that any
           | standard-conforming implementation is free to do whatever it
           | wants, meaning that different implementations may handle it
           | differently.
           | 
           | > It's not literal UB it's well known what it compiles down
           | to, every time. (.loop: jmp .loop)
           | 
           | That might be true for a particular version of a particular
           | compiler, but if you assume that it's true for all standard-
           | conforming compilers (now and in the future) then you're
           | making an assumption that is not supported by the standard.
        
           | aw1621107 wrote:
           | > It's not literal UB it's well known what it compiles down
           | to, every time. (.loop: jmp .loop)
           | 
           | ...Uh, the example shown at the literal top of the blog
           | demonstrates precisely the opposite?
        
         | mitxela wrote:
         | Ask your compiler vendor for a -fallow-infinite-loops
        
       | shmerl wrote:
       | Why does the loop mean halt in that embedded case example?
        
         | rcxdude wrote:
         | It just spins the CPU in the loop, stopping execution from
         | progressing. Technically, whether this fully halts the system
         | depends on what else is going on: you might need to fully
         | disable interrupts before entering the loop to get a full halt.
         | OTOH you can design your system so that _everything_ happens in
         | interrupts (with modern interrupt controllers the common wisdom
         | of doing as little as possible in interrupts no longer applies
         | and it can be a good way to get a predictable and low-latency
         | system) and so you finish your setup code with an infinite loop
         | to stop the CPU running off the end of your function when it 's
         | not executing one of the interrupts.
         | 
         | In a lot of cases, you might insert some 'wait-for-interrupt'
         | type instruction in the loop that halts the CPU more 'cleanly'
         | (and in a lower power mode), and usually this will appear as a
         | side-effect and keep the behaviour defined. But this is not
         | always desirable or possible.
        
       | ErikCorry wrote:
       | > [The C rule was rejected for C++ because it] could inhibit
       | useful optimizations
       | 
       | If be curious if these are the sorts of optimizations I would
       | find useful to the point where I would be happy to pay the price
       | of this annoying new behaviour.
       | 
       | Or are they just the sorts of optimizations that a compiler
       | writer finds useful who is engaged in a multi year career-
       | defining pissing contest with a competing team?
       | 
       | Don't get me wrong, I have myself engaged in a multi year career-
       | defining pissing contest with a competing team. It's fun. But
       | let's not kid ourselves that it's for the users' sake.
        
         | rcxdude wrote:
         | The bizarre thing is that the C rule is pretty deliberately
         | narrowly scoped to still enable those optimizations, and the
         | new C++ definition pretty much follows it except for this extra
         | bit they tagged on that no-one was asking for.
        
       | glum64 wrote:
       | label: goto label;
        
       | z3ratul163071 wrote:
       | c++ reaching new lows
        
         | cppcppcpp wrote:
         | This is good for Rust!
        
           | layer8 wrote:
           | This actually fixes a case that became a problem for Rust
           | implementations: https://github.com/rust-
           | lang/rust/issues/28728
           | 
           | So yes, it is good for Rust.
        
             | kibwen wrote:
             | The Rust case was fixed back in 2021 via changes to LLVM,
             | it didn't require waiting for the C++ standards body.
        
       | bitbasher wrote:
       | If an infinite loop can be both:
       | 
       | 1. An infinite busy loop.
       | 
       | 2. A thread yield/sleep.
       | 
       | It is by definition undefined behavior. You don't know what
       | you're going to get!
        
         | cppcppcpp wrote:
         | You know what you're getting, the code is right there!
        
         | mwkaufma wrote:
         | If it's one of those two it's not fully undefined behavior it's
         | just implementation defined. UB can do anything.
        
       | pianom4n wrote:
       | The abrupt shift to LLM slop halfway through is jarring and
       | disgusting to read.
        
       | 0x69420 wrote:
       | > The mentioned proposal was also accepted as a defect report, so
       | implementations may apply the fix to earlier C++ modes as well.
       | That is why you might not be able to reproduce the old behaviour
       | on a recent compiler even in C++20 mode.
       | 
       | brutal. hope major compiler vendors throw in a flag that can
       | bring some sanity to this
        
       | Panzerschrek wrote:
       | I don't see how it can be useful. It's almost always an error to
       | write such a loop. The only reason for it to exist is in very
       | low-level code to do nothing, but for such cases using something
       | like an external function written in assembly is perfectly fine,
       | no C++ standard changes are necessary. It's even makes things
       | harder by complicating the standard with little to no benefits in
       | exchange.
        
         | rcxdude wrote:
         | You can write very low-level code without mucking around with
         | assembly. For example, it's obvious that ARM's cortex-M cores
         | were designed to be possible to code for entirely in C. For
         | example, the interrupt mechanism follow the platform's calling
         | convention so an interrupt vector can just be a plain C
         | function. And something being usually an error doesn't make it
         | a good idea to be undefined, nor does it explain the behavior
         | that they have defined.
        
         | deschutes wrote:
         | On the other hand the idea that repeated iteration warrants a
         | carve out is in itself curious.
         | 
         | I'm sure there will be some bullshit example of how after
         | inlining you can find repetition like this but clearly other
         | languages get along fine without prohibiting infinite loops.
         | 
         | Furthermore, if the goal was to allow for code motion between
         | identical loops absent side effects they could have just said
         | that and spared the ordinary infinite loop.
         | 
         | In a world where C++ is a language unrelated to C another
         | reasonable position would have been to prohibit spelling loops
         | that cannot terminate and provide a fix it for the possible
         | meanings (unreachable, spin).
         | 
         | Injecting a side effect to solve this issue is just horrendous
        
       | kazinator wrote:
       | I'd much rather have the compiler diagnose an infinite loop than
       | silently pretend that it's not reachable, or that it can be
       | rewritten to a yield.
       | 
       | In other words, I am mentally well.
        
         | WalterBright wrote:
         | There's no way for a compiler to reliably determine that a loop
         | is infinite, making it very difficult for the standard to
         | require such determinations.
        
       | paparulo329 wrote:
       | The mere concept of undefined behavior is hilarious to me. "Oh
       | this part? No we can't and won't even try figuring out what doing
       | that does, this page intentionally left blank; yes we are a very
       | serious whole ass standards body thanks for asking"
        
         | layer8 wrote:
         | The purpose is to free optimizers from solving the halting
         | problem (and similar undecidable propositions), which they
         | can't. So the approach is to reduce the allowable programs to
         | those that optimizers can reliably reason about. By the very
         | nature of the problem, these programs cannot in general be
         | distinguished by an algorithm, because again that would require
         | solving the halting problem. So the non-allowable programs are
         | simply declared to be out-of-scope (aka UB).
         | 
         | It's a controversial trade-off to be sure, but it's not like
         | there isn't a sound logic to it.
        
       | Dwedit wrote:
       | "This is not simply a common pattern on bare metal -- it was also
       | undefined behaviour in C++."
       | 
       | Not just X (em dash) but also Y.
        
       | semiinfinitely wrote:
       | cant wait for ai to re-write all of the software we wrote in this
       | dogshit programming language
        
       | ratelimitsteve wrote:
       | as the kind of person who has been reading the jargon file for
       | fun since the 90s, I thought I had at least a passing familiarity
       | with a lot of hackish slang from the old days. today i learned
       | about nasal demons as a phrase for undefined behavior. i supposed
       | there's still fossils in the dirt
        
       | ahy1 wrote:
       | Optimizations are nice and all. But they should not ever be
       | allowed to change the behaviour of the program, from what is
       | expected by reading the code.
       | 
       | There are good uses for infinite loops.
        
         | mwkaufma wrote:
         | The good uses cases for _trivial_ infinite loops is a
         | vanishingly small subset of infinite loops.
        
           | ahy1 wrote:
           | It is obviously a subseet. But an important one.
        
       | Aardwolf wrote:
       | > the implementation may assume any thread will eventually do one
       | of the following: terminate, call a library I/O function, access
       | a volatile glvalue, or perform a synchronization or atomic
       | operation
       | 
       | Why is that rule needed? I could make my for loop try to solve
       | the halting problem and it'll never finish either, circumventing
       | that rule
        
         | layer8 wrote:
         | See https://news.ycombinator.com/item?id=49760653.
        
         | mitxela wrote:
         | So the compiler can merge two computation loops without proving
         | termination.
        
         | cryptonector wrote:
         | Compute the Ackermann function, write the result, terminate..
         | when the sun goes red giant and swallows the earth.
        
       | deepsun wrote:
       | > How did we get here? ... introduced in C++11 alongside
       | threading support. The standard says that the implementation may
       | assume any thread will eventually do one of the following:
       | terminate, call a library I/O function, access a volatile
       | glvalue, or perform a synchronization or atomic operation.
       | 
       | I'm more surprised it passed through the committee, they
       | should've seen that back in 2011. I can not imagine such a bug in
       | spec would pass through a Java committee, as they discuss every
       | little thing for years (sometimes decades). It's not like
       | embedded code is something new.
        
       | kmarc wrote:
       | This language/ecosystem is just crippled...
       | 
       | ... thankfully. Gives many of us well-paid jobs, and the
       | inexplicable joy of archeology (why certain decisions were made
       | at some point in the nineties, and what buggy implementation a
       | bits header is fixing).
       | 
       | And I'm not even snarky here. I kinda like to do this.
        
       | Surac wrote:
       | For me everything more than c with classes is too much cognitive
       | load. Templates my by ok for implementing Generics but most of
       | the other changes this comitee has produces are complete waste of
       | brain i think
        
       ___________________________________________________________________
       (page generated 2026-09-19 10:01 UTC)