Date: Thu, 4 Sep 2025 13:15:49 +0200
On 03/09/2025 23:07, Oliver Hunt via Std-Proposals wrote:
> I’ve replied by points below, but I just want to address the core of
> your argument.
>
> Your core argument is “signed overflow is always an error/bug”.
There are a few - a /very/ few - niche circumstances when wrapping
overflow of any kind of integer arithmetic is useful. (There are
definitely uses for modulo arithmetic, but rarely does that modulus
coincide with the size of a particular integer type. "Clock arithmetic"
is good for clocks, but not with a 2^32 or similar modulus.)
In the vast majority of cases, integer overflow - signed or unsigned -
is an error. Either their is a bug in the code which executes the
overflow, or there is a bug in the code that calls that code with
invalid inputs.
Please can we accept that, so that we don't need to keep repeating it?
It is reasonable to argue about the consequences of such bugs or runtime
errors, and very reasonable to discuss how they can be avoided in the
first place, but can we at least agree that overflows /are/ bugs?
(For the few cases where you want wrapping, there are simple and clear
ways to get it - thus language-mandated wrapping signed integer
arithmetic is never needed.)
>
> Declaring “signed overflow is UB” does not support that point of view.
>
Yes, it does.
If your execution hits a bug in your code, all bets are off - you, as
the programmer, don't know what will happen. (If you did, then you
would not have a bug in the first place.) The behaviour of the program
from then on may not necessarily be UB from the viewpoint of the C++
language standards, but it is certainly not behaviour defined by the
programmer.
The fact that integer overflow comes from bugs in code does not imply
that the language /must/ leave the behaviour undefined - but it is
certainly consistent with that choice. (It is also consistent with any
other choice of behaviour.)
> The correct way to say “signed overflow is an error” is to say “signed
> overflow is erroneous behavior”. That makes it explicit that the
> overflow is an error, and it permits developers to rely on consistent
> and deterministic behavior, rather than dealing with an adversarial
> compiler that is blindly assuming that it cannot happen.
What do you mean by "erroneous behaviour" ? What do you think the C++
standards mean by that term (define in C++26) ? Why do you think it is
in any way better than "undefined behaviour" for signed integer overflow?
The point of the new concept of "erroneous behaviour" in C++26 is to
strongly encourage (but not force) compilers to give warnings during
compile time for some diagnosable errors (such as reading local
variables before they are initialised or assigned), and to make it
acceptable for conforming compilers to add run-time checks that halt
with an error message some time after hitting "erroneous behaviour". It
does not actually make this a requirement - in effect, AFAIUI,
"erroneous behaviour" means basically the same as "undefined behaviour"
except that compilers are strongly encouraged to help developers see
such issues at compile time and/or runtime in debug or sanitising modes.
It would /not/ be a good idea to make signed integer overflow "erroneous
behaviour" because most cases cannot be spotted at compile time.
If you write :
int foo(bool b) {
int x;
if (b) return 1;
return x;
}
then the compiler can see that "erroneous behaviour" is very likely, and
complain about it. There is only a small risk of false positives or
false negatives on a "use of uninitialised local variables" diagnostic,
so that's a good thing.
If you write :
int sum(int a, int b) {
return a + b;
}
then the compiler hasn't a snowball's chance of guessing if that might
overflow or will never overflow - it all depends on the calling code.
So a diagnostic for integer overflow would be a terrible idea in general
- though fine for when the compiler /does/ know there is an overflow
(like in a constexpr evaluation). Run-time diagnostics for overflow
detection are very expensive, and thus primarily suitable for compiler
flags for use during debugging and testing. Thus signed overflow is
best as UB in the language.
>
>> On Sep 3, 2025, at 1:13 PM, Sebastian Wittmeier via Std-Proposals
>> <std-proposals_at_[hidden]> wrote:
>>
>> If something is UB in the standard, the implementations can define it
>> to be something specific instead. That is standards compliant and not
>> a new dialect, as long as the code does not depend on that outcome.
Actually, the code /can/ depend on the outcome of a documented
implementation-specific treatment of an undefined behaviour - but the
code is then non-portable. A lot of code - and almost all complete
programs - is at least slightly non-portable.
>
> Yes, but the code is wrong even if it is correct under your specific
> dialect. You cannot say “I am relying on this specific behavior” and
> then say “I am writing C++”, because your “correct” code is incorrect
> per standard C++, another compiler results in your code invoking UB.
Yes, the code is non-portable.
Code that has "int x = 100'000'000;" is also non-portable. Not all code
has to be maximally portable - indeed, very little code must be strictly
conforming. Most code sits somewhere in the middle - making common
assumptions like 8-bit char and 32-bit int and modulo conversion from
unsigned types to signed integer types. And a lot of code makes use of
some kinds of compiler extension, and is thus naturally non-portable.
>
>> > Because of that code that is correct with that flag, is not correct C++
>> But that is the point of it. Signed overflow has not correct inputs.
>> The preconditions are wrong. An implementation (and its flags) can
>> choose, what happens under those circumstance.
>
> I know that C++ says there is no correct input. Literally the /entire/
> point of what I have been saying, is that the only reason you are able
> to say "Signed overflow has not correct inputs” is because the
> specification has defined it that way. That’s a tautological argument,
> “this is undefined because we have said it is undefined”. I am saying
> “we should not be saying well defined behavior is undefined behaviour”.
>
No one says well-defined behaviour is undefined behaviour. No one is
saying signed overflow has no correct answer because the language
specifications say it has no correct answer. Signed integer overflow
has no correct answer because that's the mathematics - if you write "x =
a + b;", and "x" does not store the sum of "a" and "b", then your answer
is /wrong/. Wrapping gives incorrect answers - there is no way to get
correct answers on overflow. That's what "overflow" means!
If the US state of Indiana had passed the "pi bill", it would have given
definitions to pi as 3.2, and the square root of 2 as 10/7. These would
have been the law - they would still have been wrong. If the C++
specification defines integer addition to have wrapping behaviour on
overflow, it would be the "law" of C++ - but it would still be /wrong/.
I can't understand why this appears to be so difficult. Adding two
positive integers can never give a negative integer. If you are using a
different kind of set of numbers and different kinds of operations, you
can have different results - but then you are not adding two integers.
Having signed integer overflow in C++ be UB is /not/ about taking a
well-defined operation and behaviour and re-declaring it to be
"undefined". It is about understanding that these operations can only
work with appropriate input values - with inappropriate inputs, there is
no way to get a correct output. There is no benefit in arbitrarily
picking some particular way to produce /incorrect/ outputs simply on the
basis of it happening to match the cheapest and fastest way to make
hardware implementations. C++ is a programming language, not an
abstraction of some particular hardware - it's features are for the
benefit of programmers writing software in C++.
>
>> That is similar as if you have a contract violation for the contracts
>> feature.
>
> Yes, and I argued strongly for contract violations to be an error.
>
Contract violations are always the result of bugs in code. Precondition
violations are a bug in the calling code, postcondition violations are a
bug in the callee code (or the specifications of that code, or in the
contract itself).
Yes, integer overflow is a contract violation. And yes, it is an
"error" - depending on exactly how you define that term (the C++
standard does not, I believe, have an explicit definition).
How should these contract violations be handled? If the compiler can
see the violation at compile-time, it should give an error - that
happens already with constexpr and consteval cases. It is good for
tools to be able to check for violations at runtime - such as using the
"-fsanitize=signed-integer-overflow" option for gcc or clang, and
perhaps similar options for other compilers. It is good for tools to be
able to assume that the programmer has done a thorough job of coding and
testing, and no violations occur - that is the "trust the programmer"
philosophy that is vital to the efficiency of C and C++ and the
suitability of these languages for low-level and speed-critical work.
If you would like to make a proposal for some kind of standardisation of
the effect of "-fsanitize=signed-integer-overflow" so that there is a
portable way of getting such run-time checks on integer arithmetic, I
think that would be great. I expect there to be a lot of complications
involved, but it might be feasible.
If someone proposes that the way to handle such contract violations is
for the standard to insist on a specific incorrect result and code must
be required to continue blundering on with bad data and no help in
debugging, then that suggestion should be laughed away.
Note that the language has a great many "implicit contracts" that often
cannot be checked in any realistic way in a compiled language. An
example would be the precondition "p points to a valid object of
appropriate type" before evaluating "*p".
>> A program error was detected and it can be configured, what happens in
>> that case.
>
> I’m not sure if you’re talking about contracts (with their own issues
> w.r.t permitting UB) or overflow UB where the entire point is that the
> overflow is not being detected, the compiler is just pretending it
> cannot happen.
>
>> The program is not correct in the first place, if it comes to such an
>> error.
>
> Again, the program is only wrong because we have said it is wrong. You
> are saying we should continue to repeat choices made in the past that
> are demonstrably bad for program safety, Even though there is literally
> no reason to repeat the mistake.
>
"Literally" means something that has been written down. I have
/literally/ given multiple reasons why UB on signed overflow is a good
thing, so there /are/ literally good reasons to be consistent with that
choice (which was not a mistake).
>>
>> There is no imaginable use to have wrap-around instead (except that
>> you get a wrong, but the same behavior between different compilers).
>> But no use for wrap-around specifically. You could as well have a
>> result of 0.
>
> That’s an argument for giving defined trapping behavior, not UB.
Trapping behaviour is not appropriate in all cases. A great thing about
UB is that it could be a compiler option (as could fixing the result of
overflow at 0, or any other behaviour).
>
>> But the error is not caught either with wrap-around behavior, whereas
>> with UB the compiler may at least give an error, if it detects it at
>> compile-time.
>
> This is a misunderstanding of what UB means.
No, it is an aspect of UB. If the compiler detects that code will
execute UB, it can give a diagnostic. In some cases - compile-time
evaluated code - it is a mandated error. If overflow had defined
behaviour, it would not be an error and no diagnostic should or could be
given.
>
> The compiler does not prove that your program has invoked UB, it simply
> assumes that the UB cannot happen.
That's not what he said. Compilers /can/ give diagnostics if they can
see that the program will execute UB. It is certainly not common that
they could do so in the case of integer overflow - but it is possible,
especially in compile-time evaluated situations. Outside of that, it is
possible that compilers could sometimes detect cases from control flow
and value range analysis.
Other than that, you are correct that compilers simply assume UB cannot
happen. Basically, if they cannot prove that the programmer made a
mistake, or identify questionable constructs (based on warning flags),
they have to assume that the programmer was correct. What else could a
compiler do?
>
> You are arguing “I believe this is an error, and therefore we should
> make even more errors possible”. For example back when compilers
> silently changed a pile of secure code into exploitable code: developers
> wrote `if (a + b < a) { .. }` the _only_ reason this is “incorrect” is
> the language defining it as incorrect, the only reason it became
> exploitable is that the specification says the behavior that is well
> defined and deterministic everywhere, is incorrect, and in the early to
> mid 2000s compilers started applying this to overflow..
The only reason code like that was incorrect (assuming "b" is positive)
is that the code is nonsense.
The only reason it "worked" in some cases on compilers, giving the
results the programmer wanted, is that some compilers happened by
undocumented chance to implement the code in a way that gave particular
resulting object code.
>
> There is no reason to keep doing that.
There is certainly no reason to keep writing bad code!
> Repeating “this code is wrong
> because we have said it is wrong” is not a valid counter argument: the
> only reason it is wrong is *because* we have said it is.
>
Your argument by repetition is no more convincing now that it was earlier.
> I’ve replied by points below, but I just want to address the core of
> your argument.
>
> Your core argument is “signed overflow is always an error/bug”.
There are a few - a /very/ few - niche circumstances when wrapping
overflow of any kind of integer arithmetic is useful. (There are
definitely uses for modulo arithmetic, but rarely does that modulus
coincide with the size of a particular integer type. "Clock arithmetic"
is good for clocks, but not with a 2^32 or similar modulus.)
In the vast majority of cases, integer overflow - signed or unsigned -
is an error. Either their is a bug in the code which executes the
overflow, or there is a bug in the code that calls that code with
invalid inputs.
Please can we accept that, so that we don't need to keep repeating it?
It is reasonable to argue about the consequences of such bugs or runtime
errors, and very reasonable to discuss how they can be avoided in the
first place, but can we at least agree that overflows /are/ bugs?
(For the few cases where you want wrapping, there are simple and clear
ways to get it - thus language-mandated wrapping signed integer
arithmetic is never needed.)
>
> Declaring “signed overflow is UB” does not support that point of view.
>
Yes, it does.
If your execution hits a bug in your code, all bets are off - you, as
the programmer, don't know what will happen. (If you did, then you
would not have a bug in the first place.) The behaviour of the program
from then on may not necessarily be UB from the viewpoint of the C++
language standards, but it is certainly not behaviour defined by the
programmer.
The fact that integer overflow comes from bugs in code does not imply
that the language /must/ leave the behaviour undefined - but it is
certainly consistent with that choice. (It is also consistent with any
other choice of behaviour.)
> The correct way to say “signed overflow is an error” is to say “signed
> overflow is erroneous behavior”. That makes it explicit that the
> overflow is an error, and it permits developers to rely on consistent
> and deterministic behavior, rather than dealing with an adversarial
> compiler that is blindly assuming that it cannot happen.
What do you mean by "erroneous behaviour" ? What do you think the C++
standards mean by that term (define in C++26) ? Why do you think it is
in any way better than "undefined behaviour" for signed integer overflow?
The point of the new concept of "erroneous behaviour" in C++26 is to
strongly encourage (but not force) compilers to give warnings during
compile time for some diagnosable errors (such as reading local
variables before they are initialised or assigned), and to make it
acceptable for conforming compilers to add run-time checks that halt
with an error message some time after hitting "erroneous behaviour". It
does not actually make this a requirement - in effect, AFAIUI,
"erroneous behaviour" means basically the same as "undefined behaviour"
except that compilers are strongly encouraged to help developers see
such issues at compile time and/or runtime in debug or sanitising modes.
It would /not/ be a good idea to make signed integer overflow "erroneous
behaviour" because most cases cannot be spotted at compile time.
If you write :
int foo(bool b) {
int x;
if (b) return 1;
return x;
}
then the compiler can see that "erroneous behaviour" is very likely, and
complain about it. There is only a small risk of false positives or
false negatives on a "use of uninitialised local variables" diagnostic,
so that's a good thing.
If you write :
int sum(int a, int b) {
return a + b;
}
then the compiler hasn't a snowball's chance of guessing if that might
overflow or will never overflow - it all depends on the calling code.
So a diagnostic for integer overflow would be a terrible idea in general
- though fine for when the compiler /does/ know there is an overflow
(like in a constexpr evaluation). Run-time diagnostics for overflow
detection are very expensive, and thus primarily suitable for compiler
flags for use during debugging and testing. Thus signed overflow is
best as UB in the language.
>
>> On Sep 3, 2025, at 1:13 PM, Sebastian Wittmeier via Std-Proposals
>> <std-proposals_at_[hidden]> wrote:
>>
>> If something is UB in the standard, the implementations can define it
>> to be something specific instead. That is standards compliant and not
>> a new dialect, as long as the code does not depend on that outcome.
Actually, the code /can/ depend on the outcome of a documented
implementation-specific treatment of an undefined behaviour - but the
code is then non-portable. A lot of code - and almost all complete
programs - is at least slightly non-portable.
>
> Yes, but the code is wrong even if it is correct under your specific
> dialect. You cannot say “I am relying on this specific behavior” and
> then say “I am writing C++”, because your “correct” code is incorrect
> per standard C++, another compiler results in your code invoking UB.
Yes, the code is non-portable.
Code that has "int x = 100'000'000;" is also non-portable. Not all code
has to be maximally portable - indeed, very little code must be strictly
conforming. Most code sits somewhere in the middle - making common
assumptions like 8-bit char and 32-bit int and modulo conversion from
unsigned types to signed integer types. And a lot of code makes use of
some kinds of compiler extension, and is thus naturally non-portable.
>
>> > Because of that code that is correct with that flag, is not correct C++
>> But that is the point of it. Signed overflow has not correct inputs.
>> The preconditions are wrong. An implementation (and its flags) can
>> choose, what happens under those circumstance.
>
> I know that C++ says there is no correct input. Literally the /entire/
> point of what I have been saying, is that the only reason you are able
> to say "Signed overflow has not correct inputs” is because the
> specification has defined it that way. That’s a tautological argument,
> “this is undefined because we have said it is undefined”. I am saying
> “we should not be saying well defined behavior is undefined behaviour”.
>
No one says well-defined behaviour is undefined behaviour. No one is
saying signed overflow has no correct answer because the language
specifications say it has no correct answer. Signed integer overflow
has no correct answer because that's the mathematics - if you write "x =
a + b;", and "x" does not store the sum of "a" and "b", then your answer
is /wrong/. Wrapping gives incorrect answers - there is no way to get
correct answers on overflow. That's what "overflow" means!
If the US state of Indiana had passed the "pi bill", it would have given
definitions to pi as 3.2, and the square root of 2 as 10/7. These would
have been the law - they would still have been wrong. If the C++
specification defines integer addition to have wrapping behaviour on
overflow, it would be the "law" of C++ - but it would still be /wrong/.
I can't understand why this appears to be so difficult. Adding two
positive integers can never give a negative integer. If you are using a
different kind of set of numbers and different kinds of operations, you
can have different results - but then you are not adding two integers.
Having signed integer overflow in C++ be UB is /not/ about taking a
well-defined operation and behaviour and re-declaring it to be
"undefined". It is about understanding that these operations can only
work with appropriate input values - with inappropriate inputs, there is
no way to get a correct output. There is no benefit in arbitrarily
picking some particular way to produce /incorrect/ outputs simply on the
basis of it happening to match the cheapest and fastest way to make
hardware implementations. C++ is a programming language, not an
abstraction of some particular hardware - it's features are for the
benefit of programmers writing software in C++.
>
>> That is similar as if you have a contract violation for the contracts
>> feature.
>
> Yes, and I argued strongly for contract violations to be an error.
>
Contract violations are always the result of bugs in code. Precondition
violations are a bug in the calling code, postcondition violations are a
bug in the callee code (or the specifications of that code, or in the
contract itself).
Yes, integer overflow is a contract violation. And yes, it is an
"error" - depending on exactly how you define that term (the C++
standard does not, I believe, have an explicit definition).
How should these contract violations be handled? If the compiler can
see the violation at compile-time, it should give an error - that
happens already with constexpr and consteval cases. It is good for
tools to be able to check for violations at runtime - such as using the
"-fsanitize=signed-integer-overflow" option for gcc or clang, and
perhaps similar options for other compilers. It is good for tools to be
able to assume that the programmer has done a thorough job of coding and
testing, and no violations occur - that is the "trust the programmer"
philosophy that is vital to the efficiency of C and C++ and the
suitability of these languages for low-level and speed-critical work.
If you would like to make a proposal for some kind of standardisation of
the effect of "-fsanitize=signed-integer-overflow" so that there is a
portable way of getting such run-time checks on integer arithmetic, I
think that would be great. I expect there to be a lot of complications
involved, but it might be feasible.
If someone proposes that the way to handle such contract violations is
for the standard to insist on a specific incorrect result and code must
be required to continue blundering on with bad data and no help in
debugging, then that suggestion should be laughed away.
Note that the language has a great many "implicit contracts" that often
cannot be checked in any realistic way in a compiled language. An
example would be the precondition "p points to a valid object of
appropriate type" before evaluating "*p".
>> A program error was detected and it can be configured, what happens in
>> that case.
>
> I’m not sure if you’re talking about contracts (with their own issues
> w.r.t permitting UB) or overflow UB where the entire point is that the
> overflow is not being detected, the compiler is just pretending it
> cannot happen.
>
>> The program is not correct in the first place, if it comes to such an
>> error.
>
> Again, the program is only wrong because we have said it is wrong. You
> are saying we should continue to repeat choices made in the past that
> are demonstrably bad for program safety, Even though there is literally
> no reason to repeat the mistake.
>
"Literally" means something that has been written down. I have
/literally/ given multiple reasons why UB on signed overflow is a good
thing, so there /are/ literally good reasons to be consistent with that
choice (which was not a mistake).
>>
>> There is no imaginable use to have wrap-around instead (except that
>> you get a wrong, but the same behavior between different compilers).
>> But no use for wrap-around specifically. You could as well have a
>> result of 0.
>
> That’s an argument for giving defined trapping behavior, not UB.
Trapping behaviour is not appropriate in all cases. A great thing about
UB is that it could be a compiler option (as could fixing the result of
overflow at 0, or any other behaviour).
>
>> But the error is not caught either with wrap-around behavior, whereas
>> with UB the compiler may at least give an error, if it detects it at
>> compile-time.
>
> This is a misunderstanding of what UB means.
No, it is an aspect of UB. If the compiler detects that code will
execute UB, it can give a diagnostic. In some cases - compile-time
evaluated code - it is a mandated error. If overflow had defined
behaviour, it would not be an error and no diagnostic should or could be
given.
>
> The compiler does not prove that your program has invoked UB, it simply
> assumes that the UB cannot happen.
That's not what he said. Compilers /can/ give diagnostics if they can
see that the program will execute UB. It is certainly not common that
they could do so in the case of integer overflow - but it is possible,
especially in compile-time evaluated situations. Outside of that, it is
possible that compilers could sometimes detect cases from control flow
and value range analysis.
Other than that, you are correct that compilers simply assume UB cannot
happen. Basically, if they cannot prove that the programmer made a
mistake, or identify questionable constructs (based on warning flags),
they have to assume that the programmer was correct. What else could a
compiler do?
>
> You are arguing “I believe this is an error, and therefore we should
> make even more errors possible”. For example back when compilers
> silently changed a pile of secure code into exploitable code: developers
> wrote `if (a + b < a) { .. }` the _only_ reason this is “incorrect” is
> the language defining it as incorrect, the only reason it became
> exploitable is that the specification says the behavior that is well
> defined and deterministic everywhere, is incorrect, and in the early to
> mid 2000s compilers started applying this to overflow..
The only reason code like that was incorrect (assuming "b" is positive)
is that the code is nonsense.
The only reason it "worked" in some cases on compilers, giving the
results the programmer wanted, is that some compilers happened by
undocumented chance to implement the code in a way that gave particular
resulting object code.
>
> There is no reason to keep doing that.
There is certainly no reason to keep writing bad code!
> Repeating “this code is wrong
> because we have said it is wrong” is not a valid counter argument: the
> only reason it is wrong is *because* we have said it is.
>
Your argument by repetition is no more convincing now that it was earlier.
Received on 2025-09-04 11:15:56