The work done on PC's is totally and completely dominated by code
written in C or C++. You will probably find that all but a couple of
percent of used processor cycles is running code written in C or C++.
The programmer may have written source code in C#, or Python, or Ruby,
but the VM it runs on is in C or C++. The libraries that do the work in
the graphics, the network, the filesystems, the game engine - it's all C
and C++.
I think it is a good thing that people use different languages on PC's.
People can find a different balance between efficiency of the developer
and efficiency at run-time, or between developer knowledge and
experience and the safety of the code. Some will use low-level
languages and take care to get the details right - others will use
high-level languages and let the language runtime handle the details.
But you could say that it is precisely because a lot of modern PC
programming is done in languages other than C and C++, that it is even
more important that efficiency is a high-priority in C and C++. And it
is even more appropriate to expect the C and C++ programmers to be good
at their jobs and not need hand-holding to get simple tasks right.
So for PC's, you absolutely want to be able to conduct your integer
arithmetic as fast as possible without any unnecessary checking, traps,
exceptions, or other hinders in the flow.
As for your comments on embedded systems, I think you are seriously
misunderstanding the point when you say it is often wrapping arithmetic.
If wrapping is what you need - and it is /sometimes/ right, but not
often, even in cryptography - that's fine. But if it is wrong, it is
wrong. It really doesn't matter here if the behaviour is defined and
wrong, or undefined and wrong - except that being defined means it's
harder to find the errors and efficiency might be lower. The programmer
still needs to make the effort to make sure the inputs are valid for the
expressions in use. And when the programmer has made the effort, any
extra checks added by the compiler are a waste.
(I would like C and C++ to have unsigned types that have undefined
overflow behaviour, as found in the language Zig. I would not want to
give up on having wrapping types too - I want both.)
>> You are also failing to consider that the compression and decompression
>> relies heavily on integer arithmetic, as does all pointer arithmetic.
>>
>> It is /only/ because C and C++ compilers can generate fast code for
>> integer arithmetic that the compression and decompression algorithms are
>> fast enough to make your benchmarks run quickly.
>>
> Pointer arithmetic is also not signed integer arithmetic. So signed
> integer undefined behaviors do not help with those cases.
>
Pointer arithmetic is calculated with integer arithmetic semantics in C
and C++, and with the same kind of instructions in implementations. But
it has even more restrictions in the valid domains and it is even harder
to detect overflows. "arr[i]" means "*(arr + i)", where the expression
"arr + i" overflows on any result outside the array. Everything that
applies to signed integer overflow applies even more so to pointer
arithmetic.
>> I'll happily agree that code efficiency is not important in all code.
>> And I am more than happy to agree that code correctness is far more
>> important than code efficiency.
>>
>> But I do not accept that all correct code should be made to run
>> inefficiently just because some code is incorrect. If you want to do
>> that, pick a different programming language with a different balance
>> between efficiency and treatment of potential errors in code - C and C++
>> are not the languages for you.
>>
> Great that you agree. Note that it does not run inefficiently. There are
> not lot of "magic" performance boosts hidden in unreliability of signed
> integers. Rust for example traps in debug mode and wraps in release
> mode ... result is rather competitive in performance. Ada always throws
> and also performs not bad.
>
Rust and Ada perform reasonably, but not on a par with C and C++.
People spend vast amounts on buying a processor that is 20% faster than
mainstream alternatives - they do not want to run their code 50% slower.
Your thoughts here seems to be based on the idea that code just does a
bit of integer arithmetic every now and again, and the cpu is probably
waiting for slow main memory anyway. That's true for some code. It is
very far from true for other code.
On small systems, you are rarely waiting for memory, and individual
cycles count. You don't enable exceptions in C++, you don't stop with
error messages - you have nowhere to show an error message. Even if you
do get an error, what are you going to do about it? Recall all the
microwave ovens or oil valve controllers you've made? You write the
code correctly so that it doesn't overflow, and doesn't stop - or you
are looking for a new job. Extra checking just means bigger and more
expensive hardware to do the same job, taking extra power from your
battery life.
And if you look at PC's and big processors, you might have noticed a
trend in the last decades of more and more vector processing. While
some of that is for floating point vectors, doesn't it still give you a
clue that people /do/ care about integer arithmetic performance?
>>>> Whereas other languages are often fixed in their handling of overflow,
>>>> C++ is flexible - you can make your own classes to handle overflow just
>>>> the way you want. The language does not impose any choice on the users,
>>>> but provides the mechanisms to let you make whatever kinds of integers
>>>> you want with whatever overflow handling you want. (You can also do
>>>> C-style manual checks whenever you feel the need.)
>>>>
>>> You reply to where I mentioned that programmers can write their own
>>> trapping, (or saturating or wrapping) arithmetic using compiler intrinsics.
>>> Those are not part of C++ language. C++ language is as unhelpful as it
>>> only can. There is only undefined behavior available. How is that flexible?
>>> So compiler writers have attempted to at least help a bit.
>> I did not at all suggest that people have to use non-portable compiler
>> intrinsics. You /can/, if you want. And if you want a good, reusable
>> and efficient class, then that's what you should do - with conditional
>> compilation and generic fall-backs for portability.
>>
> Yes, I have helped to repair overflow bugs from such classes. These
> work OK.
That's the great thing about C++. Get it working right once, and use it
repeatedly.
>
>> It would be very nice to have this sort of thing as a template library
>> in the standard library, so that implementations could have optimal
>> implementations. I gather that C23 will have <stdckdint.h> with
>> functions/macros like "chk_add" that do this - I don't know if and when
>> C++ will pick it up.
>>
> I hope that quite soon. One day they should end adding (sub-bar
> for my usages) stuff like <valarray>, <regex> or <format>.
>
Different people have different needs. There's lots in the standard
library that is useless to me - but I realise it is useful to /some/ people.
>> So to be clear, I would like the standards to include this sort of thing
>> - as well as defined wrapping behaviour. But it must be in /addition/
>> to the "undefined behaviour" option, which must remain the default.
>>
> It is unclear to me why it must be default behavior. For me most
> optimizations are worth to do only in small subset of code base.
> And there other things like reducing copying, dynamic allocations or
> adding cache blocking give way more than signed integer
> micro-optimizations. Especially if those are compared to wrapping
> (that I agree is usually error). Explicit checks and branches in code
> can cost now but I'm sure that if like's of Ada and Rust gain more
> market then hardware guys will add trapping signed integers.
>
Just to be clear here - gcc (and presumably other compilers) have had
options for defined signed integer overflow for decades. You can choose
wrapping (-fwrapv) or trapping (-ftrapv). These are very rarely used in
practice - people either don't know about such things (and those
programmers would be unlikely to do a decent job of handling run-time
errors even if were the default), or they don't want such features. gcc
developers have long since realised that trying to make relatively
efficient checked integer arithmetic (as -ftrapv did) was pretty much
useless. It had significant overhead, was inconsistent, gave little
useful feedback, and only handled one small class of common errors.
Instead, they recommend more complete debugging tools such as sanitizers
that check much more and give more useful debugging feedback.
Ada is virtually dead as a language. Rust is a fad - the benefits you
get from re-writing C code in Rust can be achieved almost entirely by
re-writing the C code in decent modern C and using modern C development
tools. (Still, if it is easier to make people fix bugs by re-writing in
Rust than getting them to re-write in good C, the end result is fewer
bugs.) And if your re-write is good modern C++, even better. Why
bother with code analysis "borrow checker" tools with Rust when you can
write the code in C++ in a way that makes you sure you don't have memory
usage failures? I suppose the best you can say is that Rust makes it
marginally harder to write incorrect code, for some kinds of error.
>> You can't remove this class of undefined behaviour - not even a quarter
>> of them. The best you can do is have the compiler complain in the few
>> cases that it can clearly see a problem, such as when there is a
>> constant array index that is known to be out of bounds. But you can't
>> mandate that in the language standards, because it is highly dependent
>> on the compiler, the code, and options.
>>
> Rust attempts to do that and in quite convincing manner. It is bit raw
> yet and so tricky to populate a team but it gets better all the time.
>
You've swallowed the cool-aid. Rust, with all its bells and whistles
used correctly, is a big step up from malloc/free, and even new/delete.
But if you are happy with the overhead then you can replace you arrays
with std::vector and you don't need a half-made borrow checker to be
sure you are leak-free.
>>>
>> That is why it is vital that such mistakes in code are undefined
>> behaviour - because only then can tools add testing and trapping
>> behaviour to aid debugging.
>
> That does not follow. Defined error helps to debug lot better, as it
> is required to fail compile or run time and so can be silenced only
> with special options not achieved by using separate special tools.
I can only re-state my points in a limited number of ways. Either you
understand what I am saying, or you don't.