C++26: Trivial infinite loops are no longer undefined behaviour

(sandordargo.com)

70 points | by ibobev 21 hours ago

18 comments

  • wahern 2 hours 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 1 hour 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 1 hour ago
        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 1 hour ago
          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 42 minutes ago
            If it's a warning and not an error then emit it whether it's unreachable or not
    • rfgplk 1 hour ago
      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.

  • JoshTriplett 2 hours 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.

    • saghm 1 hour ago
      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
    • ozgrakkurt 1 hour ago
      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 1 hour ago
        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 42 minutes ago
          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?
    • rcxdude 2 hours ago
      Yeah, this is almost the worst way they could choose to 'fix' the problem.
    • ibobev 1 hour ago
      > 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.

    • ameliaquining 1 hour ago
      I'm curious, what exactly do you imagine going wrong here?
      • rcxdude 1 hour ago
        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 1 hour 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.
      • rfgplk 1 hour ago
        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.

    • marcosdumay 43 minutes ago
      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 39 minutes ago
        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 11 minutes ago
          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 0 minutes ago
            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 performance to compensate for its surprise. If I wanted a yield in my infinite loop, I'd add one.
  • omoikane 2 hours 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

    - "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...

  • peterus 2 hours ago
    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 58 minutes ago
      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)
  • ameliaquining 2 hours ago
    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
  • bitbasher 9 minutes ago
    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!

  • ErikCorry 43 minutes 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.

  • Aurornis 2 hours ago
    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 1 hour ago
      That's kind of what you get with UB; the compiler doesn't need to do what you expect.
    • mathisfun123 55 minutes 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 33 minutes 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 10 minutes ago
          probably - i guess then i meant -Werror=return-type
  • aabolfazl 2 hours ago
    Sometimes while (true){} doesn't mean anything clever. It just means the system is broken stay here.
  • MiroslavPokorny 16 hours ago
    Breadcrumbs for "blog", "year", "month" etc are broken and give 404s :(

    One can browse other blog entries so it really doesnt matter too much.

  • adzm 2 hours ago
    i have never before thought that a function could 'fall through' to another function. why does this behavior even exist?
    • kzrdude 2 hours ago
      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 2 hours ago
        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?
        • gizmo686 54 minutes ago
          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.

        • muvlon 57 minutes ago
          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.
      • raverbashing 2 hours ago
        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 1 hour ago
          With UB the compiler has no particular requirement to emit the 'ret'. (or, in the example, anything at all for the function)
    • apple1417 2 hours ago
      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 1 hour ago
        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).
    • echoangle 2 hours ago
      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 2 hours ago
        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 2 hours ago
          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 2 hours ago
            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 1 hour ago
            Yeah, that's what UB does. You get to see the arbitrary behaviour of the underlying machine with whatever the compiler produces.
    • rcxdude 2 hours ago
      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 1 hour ago
        > 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 1 hour ago
          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).
  • z3ratul163071 31 minutes ago
    c++ reaching new lows
  • glum64 41 minutes ago
    label: goto label;
  • account42 2 hours ago
    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 2 hours ago
      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:

      • pdonis 1 hour ago
        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 13 minutes ago
          __asm__ __volatile("hlt"); when doing quick and hacky debugging could work
      • account42 2 hours ago
        Low level code can and should use assembly to get the precise effect they desire in these cases.
        • echoangle 2 hours ago
          Why not just allow infinite loops instead of having me write assembly for it though?
          • account42 1 hour ago
            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 1 hour ago
              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.
        • mdspan 2 hours ago
          That would be pretty cumbersome though. If you're targeting N different architectures, you would have to write N different assembly blocks.
        • rcxdude 2 hours ago
          I shouldn't need to drop to assembly to get an infinite loop that works!
    • sumtechguy 2 hours ago
      > 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.

    • mdspan 2 hours ago
      Compilers can still diagnose something as a warning even if it's not UB.
    • not_the_fda 2 hours ago
      Interrupt driven super loops are very common on bare metal systems.
    • weinzierl 2 hours ago
      For Rust the infinite loop is important enough to have its own keyword.
      • kibwen 48 minutes ago
        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).

  • oleganza 2 hours ago
    Why is null-terminated C string considered a "billion dollar mistake", but UB isn't?
    • fooker 21 minutes ago
      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.

    • DonaldPShimoda 2 hours ago
      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 1 hour ago
        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

    • Guvante 2 hours ago
      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 2 hours ago
        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.
    • ameliaquining 1 hour ago
      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
    • nicoburns 2 hours ago
      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).
  • shmerl 1 hour ago
    Why does the loop mean halt in that embedded case example?
    • rcxdude 1 hour ago
      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.

  • adzm 2 hours ago
    as an aside, i've always preferred the zoidberg for (;;) to while(true)
    • glouwbug 2 hours ago
      I think you're thinking of (;,,;)
  • BobbyTables2 2 hours ago
    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!

    • Sharlin 2 hours ago
      The argument is that an infinite loop without side effects isn't a real program. It's not useful for anything except wasting cycles.
      • nh2 2 hours ago
        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 2 hours ago
          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 1 hour ago
            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 1 hour ago
              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?).
      • kibwen 2 hours ago
        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
      • lou1306 2 hours ago
        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.
    • bryanlarsen 2 hours ago
      They also realized that people choose compilers based on performance benchmarks, and that insane optimizations let them win.
      • vlovich123 2 hours ago
        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 2 hours ago
          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.

          • vlovich123 1 hour ago
            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 28 minutes ago
              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.

          • nicoburns 2 hours ago
            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.
    • bluGill 2 hours ago
      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.

      • dare944 40 minutes ago
        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.

      • echoangle 2 hours ago
        And how would you generate assembly to keep a microcontroller idle then?
        • bluGill 2 hours ago
          You call the CPU halt instruction.
          • echoangle 1 hour ago
            What if my CPU doesn't have that? I don't think Atmel Microcontrollers do for example.
            • bluGill 58 minutes ago
              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 12 minutes ago
                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.

            • AMDmi3 9 minutes ago
              AVRs have SLEEP instruction.