logoalt Hacker News

wahernyesterday at 3:56 PM8 repliesview on HN

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


Replies

kevin_thibedeauyesterday at 10:05 PM

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.

show 2 replies
pwdisswordfishqyesterday at 7:31 PM

GNU C does the same: memory copies can be optimized into memcpy, various operations can be realized as calls into libgcc, etc.

IsTomyesterday at 4:08 PM

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

show 1 reply
rfgplkyesterday at 4:58 PM

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.

show 1 reply
gpderettayesterday at 10:08 PM

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.

cryptonectortoday at 4:50 AM

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.

add2today at 1:03 AM

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

show 1 reply
pbalauyesterday at 6:11 PM

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

show 2 replies