If the semantics of 'while (true)' was "will crash the program after an implementation-defined but often fairly low number of iterations", I would stop using 'while (true)'.
Precisely. ISO C and C++ have a notion of various implementation limits, one of which is nested function calls due to exhausting the stack. TCO could be about a carveout on this limit.
Having said that the standards give way too much leeway for the limits, so a conforming implementation might have arbitrary limits for loops as well (at least in C++, I'm not that familiar with the C standard's wording).
I think this is no longer true. C++26 implemented a change to make trivial loops like these defined behavior (and therefore will loop endlessly as you'd expect). And this example was always defined behavior in C.
Both languages continue to have examples of slightly more complicated loops that can be assumed to terminate in the absence of side effects, but `while(true)` isn't one of those any longer.
It’s precisely not the semantics of the program that will crash the program, but the behavior of the language implementation. It’s similar to when a program in a GC language fails with OOM because the language implementation uses a no-op collector. That’s usually not part of programming language semantics.
If you wrote a correct binary search algorithm and you observed that, under one language implementation, the time complexity scaled linearly with the size of the input instead of logarithmically, you would think the semantics of the program were changed.
If you used an in-place sort algorithm and observed memory requirements that scale super-linearly with the size of the input, you would think the semantics of the program were changed.
In languages with such tail call guarantees, tail recursion _is_ a loop. It semantically encodes constant space complexity.
Programming language semantics as in https://en.wikipedia.org/wiki/Semantics_(programming_languag... is usually decoupled from space complexity. An interpreter or emulator is considered to preserve language semantics even if it changes time or space complexity.
I’m talking about how a programming language specification specifies the semantics of the programming language. It usually does not specify the time and space complexity of its basic operations, be it function calls or arithmetic operators. For example, multiplication could be implemented as O(n) repeated addition instead of in constant time. That would probably be a bad implementation (even on CPUs that only support addition), but it wouldn’t violate the semantics of the programming language.
Tail call elimination often is part of the language semantics though, for the reason others in this thread have described. E.g. Scheme specifies when a conformant implementation is required to eliminate tail calls: https://conservatory.scheme.org/schemers/Documents/Standards...
The specification allows implementations to have limits on maximum call stack depth and all sorts of other things. It's absolutely semantically meaningful in C to allocate a new stack frame.
Any function call can fail due to resource exhaustion (unless the language specification includes a mechanism to guarantee success, which hardly any language does). The specifications of the semantics of a programming language are usually silent on the behavior of programs under such resource failures; it’s outside of what is specified.
Yes, any function call can fail due to resource (i.e stack space) exhaustion. Other things, like integer addition or a while loop, can not. This is semantically relevant.