7 ms·
C++26: Trivial infinite loops are no longer undefined behaviour
- MiroslavPokorny 20d agoBreadcrumbs for "blog", "year", "month" etc are broken and give 404s :( One can browse other blog entries so it really doesnt matter too much.
- Aurornis 19d agoI 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 19d agoThat's kind of what you get with UB; the compiler doesn't need to do what you expect.
- mathisfun123 19d ago> 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 19d ago> 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 19d agoprobably - i guess then i meant -Werror=return-type
- mitxela 19d agoIn C. In C++ it's an error to not return, as it should be.
- mathisfun123 19d ago.... The blog post literally demonstrates that it is not. Feel free to repro on your own machine.
- mitxela 19d agoThe 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.
- drdexebtjl 19d agoC on the other hand puts an implicit `return 0` at the end, but only on the main function for some reason. Very weird.
- mpyne 19d agoC++ also special-cases the `main` function. Probably because `main` is the interface to the OS so it gets special language treatment.
- BobbyTables2 19d agoTLDR: 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 19d agoThey also realized that people choose compilers based on performance benchmarks, and that insane optimizations let them win.
- vlovich123 19d agoUntil 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 19d agoProbably 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 19d agoThis 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 19d agoThat'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.
- adzm 19d agoi have never before thought that a function could 'fall through' to another function. why does this behavior even exist?
- kzrdude 19d agoWell 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 19d agoBut 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 19d agoThe 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 19d agoBecause 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 19d agoThis 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
- adzm 19d agoas an aside, i've always preferred the zoidberg for (;;) to while(true)
- glouwbug 19d agoI think you're thinking of (;,,;)
- oleganza 19d agoWhy is null-terminated C string considered a "billion dollar mistake", but UB isn't?
- Guvante 19d agoNull 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.
- DonaldPShimoda 19d agoThe "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 19d agoAgreed 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 https://research.swtch.com/ub
- account42 19d agoUnfortunate. There isn't ever a good reason to have an infinite loop so concerned compilers could have just diagnosed this as a warning.
- echoangle 19d agoThe 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 19d agoLow level code can and should use assembly to get the precise effect they desire in these cases.
- mdspan 19d agoThat would be pretty cumbersome though. If you're targeting N different architectures, you would have to write N different assembly blocks.
- rcxdude 19d agoI shouldn't need to drop to assembly to get an infinite loop that works!
- echoangle 19d agoWhy not just allow infinite loops instead of having me write assembly for it though?
- account42 19d agoBecause 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.
- aabolfazl 19d agoSometimes while (true){} doesn't mean anything clever. It just means the system is broken stay here.
- JoshTriplett 19d ago> 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 19d agoYeah, this is almost the worst way they could choose to 'fix' the problem.
- ameliaquining 19d agoI'm curious, what exactly do you imagine going wrong here?
- rcxdude 19d agoThe 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 19d ago"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.
- ameliaquining 19d agoThe 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 https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1528.htm
- omoikane 19d agoThe article mentions it's a halt-on-error pattern: https://www.sandordargo.com/blog/2026/09/16/cpp26-trivial-infinite-loops#:~:text=But%20why%20would%20anyone%20write%20while%20(true)%3B%20in%20the%20first%20place%3F https://www.sandordargo.com/blog/2026/09/16/cpp26-trivial-in... Edit: sorry, missed the UB bit.
- rcxdude 19d agoThat's more why you would want them to be defined in the first place.
- Lvl999Noob 19d agoThat 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 19d agoOne 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 19d agoSee https://news.ycombinator.com/item?id=49760653 https://news.ycombinator.com/item?id=49760653.
- omoikane 19d ago> 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 https://godbolt.org/z/T65o51crx - "while(true) continue;" -> https://godbolt.org/z/Pj9raEcnP 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_Looping_Branching:~:text=Empty%20loop%20bodies%20should%20use%20either%20an%20empty%20pair%20of%20braces%20or%20continue%20with%20no%20braces%2C%20rather%20than%20a%20single%20semicolon https://google.github.io/styleguide/cppguide.html#Formatting...
- wahern 19d ago> 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 19d ago> 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 19d agoIf 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.
- peterus 19d agoThere 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 19d agoUB 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 19d agoIt'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 19d ago> 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 19d agoAsk your compiler vendor for a -fallow-infinite-loops
- shmerl 19d agoWhy does the loop mean halt in that embedded case example?
- rcxdude 19d agoIt 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 19d ago> [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 19d agoThe 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.
- cryptonector 18d agoRepeal the compiler optimizer dev employment act.
- cryptonector 18d agoThey are not useful enough for this, no.
- glum64 19d agolabel: goto label;
- z3ratul163071 19d agoc++ reaching new lows
- cppcppcpp 19d agoThis is good for Rust!
- layer8 19d agoThis actually fixes a case that became a problem for Rust implementations: https://github.com/rust-lang/rust/issues/28728 https://github.com/rust-lang/rust/issues/28728 So yes, it is good for Rust.
- kibwen 19d agoThe Rust case was fixed back in 2021 via changes to LLVM, it didn't require waiting for the C++ standards body.
- bitbasher 19d agoIf 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!
- pianom4n 19d agoThe abrupt shift to LLM slop halfway through is jarring and disgusting to read.
- 0x69420 19d ago> 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 19d agoI 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 19d agoYou 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 19d agoOn 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
- ahy1 18d agoIt is certainly not almost always an error. It is a very normal thing to do when doing embedded programming. It normally means "I do not know how to handle this error. Let the watchdog reset me."
- kazinator 19d agoI'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 19d agoThere'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 19d agoThe 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 19d agoThe 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 19d ago"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 19d agocant wait for ai to re-write all of the software we wrote in this dogshit programming language
- ratelimitsteve 19d agoas 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 19d agoOptimizations 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.
- Aardwolf 19d ago> 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 19d agoSee https://news.ycombinator.com/item?id=49760653 https://news.ycombinator.com/item?id=49760653.
- mitxela 19d agoSo the compiler can merge two computation loops without proving termination.
- cryptonector 19d agoCompute the Ackermann function, write the result, terminate.. when the sun goes red giant and swallows the earth.
- deepsun 19d ago> 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 19d agoThis 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 19d agoFor 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