Date: Wed, 3 Sep 2025 13:53:04 +0200
On 02/09/2025 21:42, Oliver Hunt wrote:
>
>
>> On Sep 2, 2025, at 7:14 AM, David Brown via Std-Proposals
>> <std-proposals_at_[hidden]> wrote:
>>
>> On 02/09/2025 15:38, Marcin Jaczewski wrote:
>>> wt., 2 wrz 2025 o 14:49 David Brown via Std-Proposals
>>> <std-proposals_at_[hidden]> napisał(a):
>>>>
>>>> On 02/09/2025 14:24, Hans Åberg via Std-Proposals wrote:
>>>>>
>>>>>
>>>>>> On 2 Sep 2025, at 14:14, Jan Schultke <janschultke_at_[hidden]>
>>>>>> wrote:
>>>>>>
>>>>>> You seem to be confusing some mostly unrelated concepts.
>>>>>>
>>>>>>>> 1. C does not allow _BitInt(1); should C++ to make generic
>>>>>>>> programming
>>>>>>>> more comfortable?
>>>>>>>
>>>>>>> The ring ℤ/2ℤ of integers modulo 2, also a field, is isomorphic
>>>>>>> to the Boolean ring 𝔹 having exclusive or as addition and logical
>>>>>>> conjunction as multiplication.
>>>>>>>
>>>>>>> If bool 1+1 is defined to 0, then it is already in C++.
>>>>>>
>>>>>> Whether there is some other C++ thing that works mathematically the
>>>>>> same doesn't say anything about whether _BitInt(1) is valid or should
>>>>>> be valid. The issue is regarding a specific type.
>>>>>
>>>>> It could have been defined to be the same as bool.
>>>>
>>>> No, it could not. _BitInt(1), if it is to exist, has the two values -1
>>>> and 0. Like all signed integer types, arithmetic overflow on it is
>>>> undefined, and like all _BitInt types, there is no integer promotion.
>>>> Thus for _BitInt(1), (-1) + (-1) is UB.
>>>>
>>> Do we need UB here? This is not `int` and we could have all operations
>>> defined. What would we gain from it?
>>
>> 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.
IME giving signed integer arithmetic overflow a defined behaviour never
improves code quality - it does not fix bugs, it does not "improve
security", it does not allow any new coding techniques of merit. It
/does/ have an impact on code efficiency, and it /does/ have an impact
on code debugging and static error checking - and those last two far
outweigh any imaginary "security" improvements.
Unsigned arithmetic is wrapping in C (and therefore C++), because very
occasionally you /do/ want wrapping behaviour. So C provides that. The
speed impact is not usually an issue because you don't usually do the
same kind of arithmetic on unsigned types - and when you use them for
things like loop indices you are typically using size_t of the same size
as pointers. (And the compiler can assume your arithmetic there doesn't
overflow, because memory does not wrap.)
Some languages - like Zig - make unsigned integer arithmetic overflow UB
as well, using the default operators. And then they provide specific
operators for wrapping integer arithmetic for signed and unsigned types,
which I think is a good solution. After all, it is the /operations/
that overflow, not the types.
> “It might be possible to optimize this” needs to stop being a
> justification for UB.
>
If you were paying attention, I gave multiple reasons for the benefits
of UB.
And if you are not interested in optimising and generating efficient
code, pick a language that supports big integers that can't overflow.
The argument that should be thrown out, IMHO, is the "undefined bad,
defined good" mantra and the mentality that goes with it. It is just
another "zero tolerance" rule used to justify zero thinking. It has all
the validity of someone claiming their PC is "secure" because they
update the OS every week.
Write your arithmetic code /correctly/, and it will not overflow. Then
it doesn't matter a bugger what the language says about overflows -
defined, undefined, saturating, triggering emails to your boss, or
whatever - because the overflow never happens. The /only/ situation in
which overflow behaviour matters is if your code is buggy and the
calculations actually overflow - in which case, defined wrapping is just
another flavour of "wrong" that is harder to catch than UB.
> The fact that we were willing to adopt 2s complement for unsigned
> arithmetic implies that the real world benefit of leaving this UB for
> signed arithmetic is extremely limited.
>
> This also ignores the increasing prevalence of bounds checks which by
> definition don’t get to pretend that overflow is not well defined on all
> hardware.
You are not just clutching at straws now, you are making these straws
out of thin air.
Bounds checks can definitely be a good thing - especially during
development and fault-finding, but also at run-time for a lot of types
of program. These checks are mostly entirely independent of overflow
behaviour and any particular hardware implementation - a check that a
container index is valid is in no sense reliant on wrapping overflow!
Except...
If signed integer arithmetic overflow is UB, then a compiler can easily
add run-time checks such as with gcc and clang's
"-fsanitize=signed-integer-overflow" flag. The developer has a quick,
simple and efficient way to check for overflow problems - as long as
some ignorant developer has not been using "-fwrapv" to sweep the bugs
under the rug to wait for after customer deployment.
I would hope you and others of your mindset have heard the phrase
"garbage in, garbage out". The way to avoid it is to stop putting
garbage in. It does not help to say "garbage in, modulo reduced garbage
out".
>
>
>> On Sep 2, 2025, at 7:14 AM, David Brown via Std-Proposals
>> <std-proposals_at_[hidden]> wrote:
>>
>> On 02/09/2025 15:38, Marcin Jaczewski wrote:
>>> wt., 2 wrz 2025 o 14:49 David Brown via Std-Proposals
>>> <std-proposals_at_[hidden]> napisał(a):
>>>>
>>>> On 02/09/2025 14:24, Hans Åberg via Std-Proposals wrote:
>>>>>
>>>>>
>>>>>> On 2 Sep 2025, at 14:14, Jan Schultke <janschultke_at_[hidden]>
>>>>>> wrote:
>>>>>>
>>>>>> You seem to be confusing some mostly unrelated concepts.
>>>>>>
>>>>>>>> 1. C does not allow _BitInt(1); should C++ to make generic
>>>>>>>> programming
>>>>>>>> more comfortable?
>>>>>>>
>>>>>>> The ring ℤ/2ℤ of integers modulo 2, also a field, is isomorphic
>>>>>>> to the Boolean ring 𝔹 having exclusive or as addition and logical
>>>>>>> conjunction as multiplication.
>>>>>>>
>>>>>>> If bool 1+1 is defined to 0, then it is already in C++.
>>>>>>
>>>>>> Whether there is some other C++ thing that works mathematically the
>>>>>> same doesn't say anything about whether _BitInt(1) is valid or should
>>>>>> be valid. The issue is regarding a specific type.
>>>>>
>>>>> It could have been defined to be the same as bool.
>>>>
>>>> No, it could not. _BitInt(1), if it is to exist, has the two values -1
>>>> and 0. Like all signed integer types, arithmetic overflow on it is
>>>> undefined, and like all _BitInt types, there is no integer promotion.
>>>> Thus for _BitInt(1), (-1) + (-1) is UB.
>>>>
>>> Do we need UB here? This is not `int` and we could have all operations
>>> defined. What would we gain from it?
>>
>> 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.
IME giving signed integer arithmetic overflow a defined behaviour never
improves code quality - it does not fix bugs, it does not "improve
security", it does not allow any new coding techniques of merit. It
/does/ have an impact on code efficiency, and it /does/ have an impact
on code debugging and static error checking - and those last two far
outweigh any imaginary "security" improvements.
Unsigned arithmetic is wrapping in C (and therefore C++), because very
occasionally you /do/ want wrapping behaviour. So C provides that. The
speed impact is not usually an issue because you don't usually do the
same kind of arithmetic on unsigned types - and when you use them for
things like loop indices you are typically using size_t of the same size
as pointers. (And the compiler can assume your arithmetic there doesn't
overflow, because memory does not wrap.)
Some languages - like Zig - make unsigned integer arithmetic overflow UB
as well, using the default operators. And then they provide specific
operators for wrapping integer arithmetic for signed and unsigned types,
which I think is a good solution. After all, it is the /operations/
that overflow, not the types.
> “It might be possible to optimize this” needs to stop being a
> justification for UB.
>
If you were paying attention, I gave multiple reasons for the benefits
of UB.
And if you are not interested in optimising and generating efficient
code, pick a language that supports big integers that can't overflow.
The argument that should be thrown out, IMHO, is the "undefined bad,
defined good" mantra and the mentality that goes with it. It is just
another "zero tolerance" rule used to justify zero thinking. It has all
the validity of someone claiming their PC is "secure" because they
update the OS every week.
Write your arithmetic code /correctly/, and it will not overflow. Then
it doesn't matter a bugger what the language says about overflows -
defined, undefined, saturating, triggering emails to your boss, or
whatever - because the overflow never happens. The /only/ situation in
which overflow behaviour matters is if your code is buggy and the
calculations actually overflow - in which case, defined wrapping is just
another flavour of "wrong" that is harder to catch than UB.
> The fact that we were willing to adopt 2s complement for unsigned
> arithmetic implies that the real world benefit of leaving this UB for
> signed arithmetic is extremely limited.
>
> This also ignores the increasing prevalence of bounds checks which by
> definition don’t get to pretend that overflow is not well defined on all
> hardware.
You are not just clutching at straws now, you are making these straws
out of thin air.
Bounds checks can definitely be a good thing - especially during
development and fault-finding, but also at run-time for a lot of types
of program. These checks are mostly entirely independent of overflow
behaviour and any particular hardware implementation - a check that a
container index is valid is in no sense reliant on wrapping overflow!
Except...
If signed integer arithmetic overflow is UB, then a compiler can easily
add run-time checks such as with gcc and clang's
"-fsanitize=signed-integer-overflow" flag. The developer has a quick,
simple and efficient way to check for overflow problems - as long as
some ignorant developer has not been using "-fwrapv" to sweep the bugs
under the rug to wait for after customer deployment.
I would hope you and others of your mindset have heard the phrase
"garbage in, garbage out". The way to avoid it is to stop putting
garbage in. It does not help to say "garbage in, modulo reduced garbage
out".
Received on 2025-09-03 11:53:08