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 22:59:08 +0200
On 04/09/2025 15:33, Marcin Jaczewski wrote:
> czw., 4 wrz 2025 o 14:51 David Brown <david.brown_at_[hidden]> napisał(a):
>>
>> On 04/09/2025 10:39, Marcin Jaczewski wrote:
>>> czw., 4 wrz 2025 o 08:32 David Brown <david.brown_at_[hidden]> napisał(a):
>>>>
>>>>
>>>>
>>>> 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.
>>>>
>>>
>>> To be devil's advocate, UB is worse as it is "viral", you not only get
>>> the wrong value but
>>> compiler can do unexpected things, like remove `if` that program tries
>>> to use to detect overflow.
>>
>> /All/ incorrect code is viral in this sense. Once you've got a bug,
>> things can go badly wrong very quickly. If your calculations don't give
>> the correct value - the value that the programmer expects - then
>> everything afterwards that assumes the code was correct, is in jeopardy.
>> If you have a routine calculating the thrust rate for a rocket, and
>> the calculations overflow, does it really matter if the next step is to
>> assume that an overflow could never have happened, or if you run with a
>> big negative thrust rate? (And if you had the foresight to check for
>> negative thrust rates, you should have had the foresight to check for
>> bad values before doing the overflowing calculations.)
>>
>> It is arguably the case that UB can lead to disaster a bit quicker, but
>> getting meaningless well-defined results is not different in principle,
>> and not significantly less bad in practice.
>>
>
> My point was that you could manually detect overflow if its not UB and
> react accordingly.
> UB sometimes makes it very hard to do some naive checks as they are UB itself
> and can be removed by the compiler. And this is probably the biggest gripe that
> people have with UB.

That's just nonsense. Seriously - there is no need to have wrapping
integer overflow in order to detect problems.

Sanitise your inputs from outside sources (like files, network packets,
etc.). Know what ranges your data has. Make sure you call your
functions with appropriate inputs. Use appropriate types to hold the
data and do the operations - this is all basic good programming
practice. And if you /really/ think that wrapping behaviour is what you
want, it's not hard to get with a couple of casts to unsigned types.
Unless you are dividing (which can't overflow anyway, except for int_max
divided by -1), with two's complement hardware you can cast to unsigned,
do the calculations, and cast back to signed again.

Easiest of all is usually to simply use int64_t. You have to try
*really* hard to overflow an int64_t. And as a byproduct, your code
will usually be smaller and faster on modern cpus than if you used "int".

There was a time when compilers were very weak, integer types were
small, and programmers tried all sorts of tricks to get efficient
results. They placed fast and lose with the C and C++ semantics, and
wrote code that was highly compiler-dependent to get the optimal object
code. There was a time when people had excuses for wanting wrapping
signed integer overflow, or relying on the results of UB, and could
point to generated code to say that they needed to use such tricks.

Those times are long gone. 64-bit integer arithmetic doesn't overflow
without completely nonsense code. Compilers are happy with casting back
and forth, and are not going to give inefficient results afterwards.
Ugly but correct code can be hidden in inline functions and used in nice
user code without efficiency penalties. Code can be written in the
source code in a way that is correct according to the language semantics
- and if it could be implemented in object code more efficiently by
taking advantage of hardware that has wrapping operations, there's a
fair chance the compiler will implement it that way. And if you really
want to do your sums and look for overflow afterwards, C++26 has you
covered with the chk_add and similar functions.


> This mean we have diffrent types of "virality", first only cascade
> with propagation of bad value
> and second one UB, affects everything around,
> like after `MAX_INT+1` compiler can delete the rest of the code in
> function as its "impossible" for a program
> to call this code legally.

I don't care if my program fails because bugs in my code caused the
compiler to remove part of the code, or because bugs in my code with
well-defined calculations gave an answer I was not expecting which led
to failure of the program. At least with the UB version there's a
chance the compiler could warn me, a chance that I'll notice missing
code when debugging, and a solid chance that runtime sanitisers could
catch the problem.

>>
>>> One thing for overflows to be "illegal" is that the program is
>>> portable to other sizes of `int`.
>>> Same program that works with `int` that has size of 16bit and does not
>>> have UB, it will work
>>> on machines that have 64bit int.
>>>
>>
>> Code that is correct for a 16-bit int machine, with today's UB on
>> overflow semantics, will be correct on a 64-bit int machine (or more
>> realistically, a 32-bit int machine).
>>
>> The same would apply for erroneous behaviour on overflow - it would have
>> no benefit or disadvantage, because hitting erroneous behaviour is as
>> much a bug in your code as hitting UB.
>>
>> Code that is correct for a 16-bit int implementation that relies on
>> wrapping behaviour, on the other hand, will be wrong on a 32-bit int
>> machine.
>>
>>
>
> This was my point here, if code works on 16bit without triggering UB
> then its will work the same on 64bit or 128bit.
>

Yes - as long as it is written in a language that does not have defined
wrapping behaviour for integer overflow. Your point is another argument
in favour of UB over defined overflow behaviour.

>
> I overall understand why UB is needed but I would not ignore the fact
> that it has a lot of bad drawbacks.

Of course there are some drawbacks - but the main thing to remember
about UB is that you only get it if your code has errors. I am well
aware that writing perfect error-free code is no easy matter, but I
don't see UB as making it worse.

> And I would always check if this UB is really needed in the given case.

Received on 2025-09-04 20:59:16