C++ Logo

std-proposals

Advanced search

Re: [std-proposals] D3666R0 Bit-precise integers

From: David Brown <david.brown_at_[hidden]>
Date: Thu, 4 Sep 2025 08:32:08 +0200
On 03/09/2025 21:09, Oliver Hunt wrote:
>
>
>> On Sep 3, 2025, at 4:53 AM, David Brown <david.brown_at_[hidden]> wrote:
>>>>
>>>> Do we /need/ UB on signed arithmetic overflow? No. Do we /want/ UB
>>>> on signed arithmetic overflow? Yes, IMHO. I am of the opinion that
>>>> it makes no sense to add two negative numbers and end up with a
>>>> positive number. There are very, very few situations where wrapping
>>>> behaviour on signed integer arithmetic is helpful - making it
>>>> defined as wrapping is simply saying that the language will pick a
>>>> nonsensical result that can lead to bugs and confusion, limit
>>>> optimisations and debugging, and cannot possibly give you a
>>>> mathematically correct answer, all in the name of claiming to avoid
>>>> undefined behaviour.
>>> This as argument for unspecified or implementation defined behavior,
>>> not introducing a brand new type, with *all* of the known security
>>> issues of `int` (that we decided were not necessary for the
>>> performance of unsigned integers).
>>
>> "int" does not, in itself, have "security issues". Incorrect
>> programming can have security implications. Overflow - whether
>> wrapping or UB, or anything else - is usually a bug in the code.
>> Giving a fixed definition to the incorrect value does not magically
>> fix the error in the code.
>
> No, Any new feature that introduces UB for no reason is a security
> feature, for the same reason that creating new types that introduce
> lifetime issues when used in a trivially obvious way (think: returning a
> string_view).
>

Those are two wildly different things.

Undefined behaviour is not in itself a security risk. /Incorrect/
behaviour is a security risk. It does not matter whether a program bug
has undefined behaviour, or defined and documented behaviour that is
different from expected. If a program hits a bug in the code, it is now
no longer doing what the programmer intended and predicted - and /that/
is a security risk (for code that is relevant for security).

It does not matter if "a + b" overflows and has undefined behaviour, or
if "a + b" overflows and has defined but unexpected behaviour, such as
suddenly jumping in value.

What always matters when considering new features, is how hard it is to
use them incorrectly, how easy it is to use them correctly, and how easy
it is to see if they are being used correctly. So if string_view's can
easily have lifetime issues leading to incorrect behaviour (it doesn't
matter if it is UB or tightly defined incorrect behaviour) just by
obvious use such as returning them from a function, then that
language/library feature is badly designed.

Everyone already knows that integer overflow is a bad thing - your code
will be wrong if you overflow signed integer arithmetic. People who
don't understand that should go back to beginners courses in C or C++ -
this is nothing new, and has existed since the beginning of C. It
applies equally to every programming language that has limited sizes for
integers - it does not matter whether the language says the operations
are UB or wrapping. Even if the language says there will be a runtime
error of some sort, it is almost certainly a control path that the
programmer never considered and never tested - a bug.

So having signed _BitInt arithmetic overflow being UB is nothing new,
nothing surprising, and works exactly like C and C++ programmers expect.
  Saying that they are defined as wrapping, on the other hand, would be
almost entirely useless to programmers because the results are still
mathematically wrong, and unnecessarily complicate the language by
making them different from other signed integer types for no good reason.

>
> Integer overflow has defined behavior, in languages without backwards
> compatibility constraints overflow is defined as trapping, and they
> provide language specified mechanisms to perform wrapping arithmetic.
> Yay for them.

First off - no, there is no consensus amongst other languages for how
integer overflow works. Secondly, wrong behaviour is wrong behaviour.
Defined wrong behaviour is just as wrong as undefined wrong behaviour.
The idea that putting a definition on things is a silver bullet to fix
code is like the old joke - "How many Microsoft engineers does it take
to change a lightbulb? None - they just declare darkness to be the new
standard."

>
> In C++ we have refused to address this in any standardized way: there is
> *no* mechanism to say “I want wrapping” or “I do not want wrapping”, and
> there is no way to perform wrapping arithmetic at all.

If you want to propose a nice standardised way to ask for wrapping
operations in specific cases, /that/ is an idea I can support. Zig has
UB on overflow for normal operations like "a + b", but has additional
wrapping operators like "a %+ b". (They have the same thing for signed
and unsigned types - they understand that it is operations that
overflow, not types.)

C++26 has saturated arithmetic and overflow checked arithmetic
functions. These are fine in themselves - they do the job. But they
are not as convenient to use in code.

It is also perfectly possible to define these things yourself. You can
make an entity "wrapping" so that "a +wrapping+ b" gives a wrapping
addition operation. It needs no language change - and means you could
write out a library here (with +saturating+, +throwing+, +ub+ and other
possible alternatives) and propose it for the standard.

>
> There is one *potentially* unsafe overflow behavior: wrapping.

No, you are wrong on both accounts here. First, wrapping is /actually/
unsafe - unless the programmer takes into consideration that their
expression may overflow and handles it appropriately. And if the
programmer has thought that far ahead, he/she can handle the situation
far better in multiple ways - making sure the values used are small
enough that there is no risk of overflow, or using bigger types, or
re-arranging the code to remove the overflow risk. At no point would
wrapping be helpful.

Secondly, other defined overflow behaviours like saturation or throwing
an exception are as much "potentially safe/unsafe" as wrapping is.

>
> I have certainly _never_ heard a justification for signed overflow being
> UB that was not “it allows optimizations” - I have never heard any
> argument that developers expect UB, or there are machines that produce
> undefined behavior on this overflow.

You /have/ heard other justifications for signed overflow UB - from me
at least. The main ones (other than efficiency - which is a /good/
reason) are:

UB-overflow ints are a closer model to mathematical integers, because
more useful mathematical integer properties and identities still hold.
This makes it easier to reason about code and prove correctness of code
(as long as you keep your inputs in range!).

UB-overflow means compilers and other static analysers can spot more
errors at compile-time, because overflow is always an error and not
something that the programmer /might/ have wanted.

UB-overflow means compilers and debuggers can have run-time checks added
to catch overflows during testing - if behaviour is defined, the
overflows are just part of the normal execution and you don't see the
effect of the bugs until later in the code.

UB-overflow means compilers can offer additional options such as
run-time checks, throwing exceptions, and even wrapping as ways to
handle overflow bugs.

UB-overflow means it is entirely clear to programmers that overflow is a
problem and indicates a bug in the code. Defined wrapping gives a false
sense of security and makes programmers think they don't have to
consider overflow - leading to bugs.


(I'm snipping stuff because this thread is getting really out of hand.
Unless someone wants to make a general "make all signed integer
arithmetic defined as wrapping" proposal, it is perhaps better suited
for something like the comp.lang.c++ Usenet group. Note, however, that
people have already proposed making signed arithmetic overflow wrapping,
and the C++ committee have already voted on it and rejected it decisively.)

Received on 2025-09-04 06:32:13