Integer arithmetic when overflow exists

Ö

Öö Tiib

Reading this thread and seeing the flame war and thinking I suddenly
realized that it is not fault of gcc it has such overabundant
opportunities to joke in situations. May be it even has to joke indeed
on UB to get best performance on non-UB. So ... it is fault of the
language.

Why integer arithmetic of C++ does not ever stop of being that
"everything is UB" nonsense? Lot more logical would be to exactly define
what will or will not happen if:
1) std::numeric_limits<int>::is_modulo is true
2) std::numeric_limits<int>::has_quiet_NaN is true
3) std::numeric_limits<int>::traps is true
etc.

Otherwise what is the point to have such verbose crap like
'std::numeric_limits' in library if those constexpr bools have outright
zero behavioral connection to actual arithmetic done (or not done or
optimized out)?

I see that for example integer arithmetic with 'has_quiet_NaN' would
open up whole pile of additional optimization and arithmetic with
'is_modulo' would turn that 'a < a + 1 -: true' off by definition.

C++ can even let us to decide ourselves what to do for example on
arithmetic overflow. It looks cheap enough, clear enough, easily
configurable and beneficial to performance. Like gcc thankfully already
does with that -fwrapv.

So why not?
 
A

Alf P. Steinbach

On 11/10/13 16:03, Alf P. Steinbach wrote:

But anyway, what does MinGW g++ do?

I'm no wizard with g++ options, so maybe the example below lacks the
specific option that will cause a bit of nuclear fireworks.

I'm just hoping "-ofast" will suffice to get that awfully expensive and
unacceptable integer comparison in the "if" condition, removed:

[example]
[D:\dev\test]
version g++
g++ (GCC) 4.7.2

[D:\dev\test]
g++ foo.cpp -fno-wrapv -Ofast
[D:\dev\test]
a
x*a = 2147483647, x+b = -2147483648
Oh thank Odin! The war is over!

[D:\dev\test]
[/code]

Oh dear also g++ produced wrap-around behavior.

Which means that changing the standard in that direction would not
introduce any new inefficiency (assuming for the sake of discussion that
it is an inefficiency, which I'm not sure I agree with), but instead
capture existing practice, at least for these two compilers.

I've just tested gcc (version 4.5.1) with your code. The only change I
made was using "const char*" instead of "auto const", because my gcc is
a little older. And I have no "-Ofast", so tested with -O2 and -Os:


$ g++ t2.cpp -o t2 -Os -Wall && ./t2
x*a = 2147483647, x+b = -2147483648
Oh thank Odin! The war is over!

$ g++ t2.cpp -o t2 -O2 -Wall && ./t2
t2.cpp: In function ‘void foo(int) [with int a = 1, int b = 1]’:
t2.cpp:30:31: instantiated from here
t2.cpp:17:5: warning: assuming signed overflow does not occur when
assuming that (X + c) >= X is always true
x*a = 2147483647, x+b = -2147483648
Firing nukes at ourselves!

Thanks for testing that :).

Using -O2 makes no difference here :-(, with my main g++ installation,
but it's nice to see the issue for real.

Note that execution of the "if" body with a "false" "if" condition that
is assumed by the programmer to hold in the "if" body, caused
self-immolation -- broken assumptions generally do wreak havoc.

Also note that the behavior is different with optimization (release
build) and without (which can be the case for a debug build), with no
apparent problem for the debug build.

So the gcc optimization yields

(1) broken assumptions, perhaps thereby causing a nuclear attack on
one's own position, and

(2) possibly/probably active sabotage of debugging efforts to fix that,
with no problem apparent in the debugging

which nastiness IMHO is a pretty steep cost for avoiding one or two
machine code instructions in a rare special case.

We are talking about /undefined behaviour/ here.

Yes, that's what this sub-thread is about and has been about, a special
case of formal UB.

Good observation.

And with well-defined signed arithmetic -- modular -- one would have
avoided that UB.

And one would then therefore also avoid the broken assumption, and
therefore also the resulting self-immolation or other unplanned effect.

As mentioned earlier, and I think you agreed with that, formal UB is not
a carte blanche for the compiler to do unreasonable things, such as
removing the function call in "x = foo( i++, i++ )", or removing whole
loops and, except when the programmer tries to find out what's going on
by debugging, executing "if" bodies when their conditions don't hold.

If you want to check
reality, you will have to do /far/ better than a single check of a
couple of compilers on one platform.

Give me test results from a dozen code cases in each of C and C++,
compiled with multiple compilers (at least MSVC, gcc, llvm and Intel),
with several versions, on at least Windows and Linux, each with 32-bit
and 64-bit targets, and each with a wide variety of command line
switches. Show me that /everything/ except gcc /always/ treats signed
overflow as modular, and I start to consider that there might be a pattern.

Then I'll send you to check perhaps 40 different embedded compilers for
30 different targets.

I believe those requirements are far more stringent than the reality
checking before the decision to make std::string's buffer guaranteed
contiguous, at the committee's meeting at Lillehammer in 2005.

However, while your requirements are unrealistic it's unclear what they
are requirements for.

Some others and I are discussing the issue of whether it might be a good
idea (or not) to make signed arithmetic formally modular. As I've shown
you by linking to Google Groups' archive, this is an old discussion in
clc++. I referred to a thread 9 years ago, but it goes even further
back: most arguments are well known; regarding this issue you have
contributed nothing new so far.

But your requirements statement above indicates that you are discussing
whether gcc is alone in its treatment of signed overflow.

I don't know, but I do think you're alone in discussing that.

Alternatively, I'll settle for quotations from the documentation for
said compilers guaranteeing this behaviour, if you don't want to test
them all.

I'll just note now that with guaranteed modular signed arithmetic, one
would more probably add an assertion about e.g. "x+b > 0".

Such simple assertions would then not only most probably catch the
invalid actual argument, but the added knowledge could in many cases be
used by the compiler to optimize the code.

Of course, instead of CHANGING the semantics of existing types, one
could introduce new types, perhaps via a header like <stdint.h>.

In passing, new types are also IMO a good solution for gcc's problems
with IEEE conformance (it's of two minds because semantics changes with
options, and so it reports false guarantees via numeric_limits).

So, the general idea is a win-win-win: getting rid of UB, detecting bugs
up front, getting optimization without counter-intuitive cleverness --
and incidentally getting rgw same behavior for release and debug
builds of the code, which I think is very much desirable.


- Alf
 
T

Tobias Müller

Just for my understanding, what do you mean by "optimization of the runtime
behavior"? In the end, all optimizations are affect the runtime behavior
somehow, otherwise they would not be optimizations.

[...]
Even if you try to ignore it, this is a major point and IMO outweights the
downsides, that only happen in bad code.
So? How do you propose to avoid all such code?

If a programmer knowingly and carelessly relies on UB, I expect his code to
contain more flaws of the same kind that are not immediately visible.
Probably he also assumes a specific sizeof(int) for his undefined signed
integer overflow.
Despite your experience, in this case I trust the llvm devs. Please don't
take this as a personal attack, it's not.

It's OK, my explanation was just not clear enough. So, thanks for that feedback. ;-)

In particular, for the example

for (i = 0; i <= N; ++i) { ... }

the description "if the variable [namely 'i'] is defined to wrap around
on overflow, then the compiler must assume that the loop is possibly
infinite" is false. The author may be thinking of the case where N is the
maximum signed value, but that's easy to check for, and even easier for
the programmer to specify if e.g. loop unrolling is desired.

I omitted this sentence continuation for brevity in the quote above: "-
which then disables these important loop optimization".

Even if you disagree with the notion that "must assume" is false, clearly
the bit about disabling the optimization is false.

After all, they can easily be done, which contradicts that they cannot be done.

Is that clear enough explanation?

Oh well, to prove that for yourself if you don't see it, simply look up
Duff's Device in Wikipedia (if you're not familiar with it), and
hand-craft that optimization for a relevant loop with signed loop
variable where N can possibly but necessarily be INT_MAX.

So loop unrolling is possible even in the case of an infinite loop, which
intuitively makes sense. I expect that the clang devs know that, so I guess
they had different optimizations in mind.
So, even more importantly than the incorrect "must assume", the claim
that this loop would be impossible to optimize is completely and utterly false.

Well, the compiler must assume, that there are cases where the loop does
not terminate. And if necessary handle them differently.

AFAIK, optimizations considered for -O2 usually don't trade binary size for
speed, so this would disable those optimization for the typical setting.
And so, with errors and misdirections galore, all of them pointing in one
direction, for the purely technical, for what you assume internally for
your programming, it's NOT a good idea to trust that document.

Even if you don't agree with the reasoning, it is still helpful because it
points out many possible traps.
Rather trust your instinct, that when you initially find that, as you
wrote, you "don't fully understand" it, and it's about something simple,
then there's most likely something fishy, something not right, and when
written by intelligent people it's likely misdirection. :)

I don't think like that. Rather I try to understand it before I make up my
opinion. Even more in cases like this where the author is much more
involved in the matter than I am.
No, that would be a rather impractical approach.

Can you think of anything more practical?



No, but knowledge about N has to be established either at compile time or at run time.

The case where N is known at runtime is not interesting. That's an easy
target, regardless whether it is UB or modular.
Note that this is the same as for unsigned loop variable.

I expect that there are optimizations where you don't need any runtime
information about N other than that the loop always terminates.
Effectively, therefore, the author(s) pretend that people are using
signed loop variables in order to allow gcc to silently exploit formal UB
to optimize away two machine code instructions for the case where N is
not known to be in reasonable range, and that it's important to have this
micro-optimization invoked in that very subtle and implicit way.

Which is just silly.

At least when you reflect on it a bit.

If I reflect a bit on that example above, I come to the conclusion that an
infinite loop in one single corner case is certainly not expected behavior.
You are argumenting against your own principle of least surprise.

With UB, the loop works as expected even if you forget to validate N.
No, but that's the worst case.

"No, but..."?
One far more PRACTICAL approach is that the compiler warns when the loop
can be infinite and unrolling has been asked for; then it can be
rewritten or an option supplied that says "assume finite loops".

And what do you do if you encounter that warning but know that you
validated N and it is safe? In the end all warnings should be eliminated.
Same with unsigned loop variable. ;-)

Here the solution is: take a signed loop variable.

Tobi
 
T

Tobias Müller

Alf P. Steinbach said:
Perhaps you could give a CONCRETE EXAMPLE?

Because I'm baffled as to how "x < x+1" could be produced, with x a
variable and the original code meaningful.

inline void foo(int x, int y)
{
if (y < x + 1)
{
//...
}
}

int a = ...
f(a, a);

No template needed, inlining is enough.

Tobi
 
R

Rupert Swarbrick

Oh well, to prove that for yourself if you don't see it, simply look up
So loop unrolling is possible even in the case of an infinite loop, which
intuitively makes sense. I expect that the clang devs know that, so I guess
they had different optimizations in mind.

I don't know much about clang, but here's a couple of examples where GCC
cares, at least. GCC has different ways to try and optimise a tight
loop. Unrolling is one. Another is modulo scheduling, which can get
impressive performance gains without the code bloat you get with
unrolling. However, designing (or computing) a modulo schedule is really
rather hard, so GCC just gives up if the loop isn't a "simple loop". A
simple loop is defined to be one where you know at the start how many
times you're going to go around.

Another example where I might care about this is with a "zero overhead
loop" instruction. They tend to be of the form

loop some_register, some_label
do_stuff
some_label:

where some_register contains the number of iterations to do. Of course,
if the compiler can't work out how to put the number of iterations into
some_register in advance, it won't be able to use this.


Rupert
 
J

Jorgen Grahn

.

It looks that way in the web page, but as I noted in a semi-recent
thread there's a lot of std regex code in the library and it has been
there for years. As far as I can tell the most useful parts are
probably there.

Perhaps someone who has actually /used/ it can comment? I haven't
written a line of C++11 yet; I'm still at TR1.

/Jorgen
 
A

Alf P. Steinbach

On 11/10/13 16:03, Alf P. Steinbach wrote:

<snip code>

But anyway, what does MinGW g++ do?

I'm no wizard with g++ options, so maybe the example below lacks the
specific option that will cause a bit of nuclear fireworks.

I'm just hoping "-ofast" will suffice to get that awfully expensive and
unacceptable integer comparison in the "if" condition, removed:

[example]
[D:\dev\test]
version g++
g++ (GCC) 4.7.2

[D:\dev\test]
g++ foo.cpp -fno-wrapv -Ofast

[D:\dev\test]
a
x*a = 2147483647, x+b = -2147483648
Oh thank Odin! The war is over!

[D:\dev\test]
_
[/code]

Oh dear also g++ produced wrap-around behavior.

Which means that changing the standard in that direction would not
introduce any new inefficiency (assuming for the sake of discussion
that
it is an inefficiency, which I'm not sure I agree with), but instead
capture existing practice, at least for these two compilers.

I've just tested gcc (version 4.5.1) with your code. The only change I
made was using "const char*" instead of "auto const", because my gcc is
a little older. And I have no "-Ofast", so tested with -O2 and -Os:


$ g++ t2.cpp -o t2 -Os -Wall && ./t2
x*a = 2147483647, x+b = -2147483648
Oh thank Odin! The war is over!

$ g++ t2.cpp -o t2 -O2 -Wall && ./t2
t2.cpp: In function ‘void foo(int) [with int a = 1, int b = 1]’:
t2.cpp:30:31: instantiated from here
t2.cpp:17:5: warning: assuming signed overflow does not occur when
assuming that (X + c) >= X is always true
x*a = 2147483647, x+b = -2147483648
Firing nukes at ourselves!

Thanks for testing that :).

Using -O2 makes no difference here :-(, with my main g++ installation,
but it's nice to see the issue for real.

Note that execution of the "if" body with a "false" "if" condition that
is assumed by the programmer to hold in the "if" body, caused
self-immolation -- broken assumptions generally do wreak havoc.

If the programmer makes assumptions that are broken, then havoc will
occur. Other than that, your paragraph makes no sense to me.

Programmers, although perhaps not you, are fallible and make mistakes
all the time.

However, you are conflating

* the assumption of the programmer making a call with invalid argument
value, with

* the assumption of the programmer coding up a function as if an "if"
block would only be executed when its condition holds, i.e. just
assuming a reasonable compiler.

The first point is about the kind of mistakes programmers make all the
time, and that they are not surprised to discover does not hold.

The second point is different.

One is pretty surprised when that happens, when the control flow is
other than specified in the program text.

I suspect that your conflation of these issues, of what the programmer
does versus what the compiler does, is intentional, because it's near
impossible to write coherent text yet make incorrect and misleading
conclusions about just every issue, consistently over time, as you have
done.

Anyway, it's easy to support the programmers, and it's also easy to make
their work perversely difficult, and gcc goes the second route.

With modular arithmetic as standard one avoid all that.

If you are using different options for releases from those for debugging
(other than early testing, or perhaps special case debugging) then your
development process is broken.

If the debug and release build options are identical, then there's no
difference between debug and release builds.

Hence your statement is meaningless, balderdash, rubbish.

I can't imagine what kind of reader you're writing for.

Test what you ship, ship what you test.

That's good advice, but very misleading in context -- you're implying
that someone had argued differently.

Let's be 100% clear here - it is the programmer's assumptions that are
broken. The compiler is correct.

And so, when a person hurts himself or others with a power tool that
instead of safety features has error amplification, you refuse to admit
that the tool might be improved.

It's the (imperfect) user's fault, yeah.

That's dumb.

So gcc's optimisation demonstrates that people who do not understand the
undefined behaviour of signed overflow in C and C++ are not qualified to
write nuclear attack code. I think we already knew that.


Are you /really/ suggesting that gcc optimises code according to the C
and C++ standards, and according to normal programmer's understandings,
as some sort of method of deliberately causing /you/ trouble during
debugging of invalid code?

From here on and onward you're arguing AS IF your opponent engages in
dangerous habits, doesn't understand basics, etc. ad nauseam, and you do
that even after having been proven wrong about a number of basic
technical issues, and after displaying lack of knowledge about history,
terms, standards, etc. -- and after unsuccessfully having tried that
"discredit" tactic a number of times, and been caught at it.

That's also a pretty dumb thing to do, sorry.


[snip a lot more like that]

- Alf
 
Ö

Öö Tiib

We have already covered the fact that modular behaviour of signed
integers is counter-intuitive, and is at best one of many possible
choices for defining behaviour.

Implementation /has/ to specialize 'std::numeric_limits<int>'.
It has 'is_modulo', 'has_quiet_NaN', 'traps' and pile of others. So
why do those things only affect inline assembler? Why there are no
relation between arithmetic of numeric types and traits of the very
same numeric types? Why there has to be that dreaded UB?
 
Ö

Öö Tiib

No - there is no "release" and "debug" build. There is only a "release"
build (plus any other temporary ones to aid early development or
specific test setups). That is the whole point - you should never have
a "release" and "debug" build setup. It's a common mistake, but it is
still a mistake.

Really? All debuggers I have seen fail to match optimized programs back
to variables and original code so that pretty much makes debuggers useless
and debugging several times more expensive.

Unoptimized code with heavy usage of templates however is often performing
worse than similar code on Python interpreter.

So the "common mistake" seems to be winning strategy here. :D
 
A

Alf P. Steinbach

On 11/10/13 23:10, Alf P. Steinbach wrote:
On 11.10.2013 16:43, David Brown wrote:
On 11/10/13 16:03, Alf P. Steinbach wrote:

<snip code>

But anyway, what does MinGW g++ do?

I'm no wizard with g++ options, so maybe the example below lacks the
specific option that will cause a bit of nuclear fireworks.

I'm just hoping "-ofast" will suffice to get that awfully expensive
and
unacceptable integer comparison in the "if" condition, removed:

[example]
[D:\dev\test]
version g++
g++ (GCC) 4.7.2

[D:\dev\test]
g++ foo.cpp -fno-wrapv -Ofast

[D:\dev\test]
a
x*a = 2147483647, x+b = -2147483648
Oh thank Odin! The war is over!

[D:\dev\test]
_
[/code]

Oh dear also g++ produced wrap-around behavior.

Which means that changing the standard in that direction would not
introduce any new inefficiency (assuming for the sake of discussion
that
it is an inefficiency, which I'm not sure I agree with), but instead
capture existing practice, at least for these two compilers.

I've just tested gcc (version 4.5.1) with your code. The only
change I
made was using "const char*" instead of "auto const", because my
gcc is
a little older. And I have no "-Ofast", so tested with -O2 and -Os:


$ g++ t2.cpp -o t2 -Os -Wall && ./t2
x*a = 2147483647, x+b = -2147483648
Oh thank Odin! The war is over!

$ g++ t2.cpp -o t2 -O2 -Wall && ./t2
t2.cpp: In function ‘void foo(int) [with int a = 1, int b = 1]’:
t2.cpp:30:31: instantiated from here
t2.cpp:17:5: warning: assuming signed overflow does not occur when
assuming that (X + c) >= X is always true
x*a = 2147483647, x+b = -2147483648
Firing nukes at ourselves!

Thanks for testing that :).

Using -O2 makes no difference here :-(, with my main g++ installation,
but it's nice to see the issue for real.

Note that execution of the "if" body with a "false" "if" condition that
is assumed by the programmer to hold in the "if" body, caused
self-immolation -- broken assumptions generally do wreak havoc.


If the programmer makes assumptions that are broken, then havoc will
occur. Other than that, your paragraph makes no sense to me.

Programmers, although perhaps not you, are fallible and make mistakes
all the time.

That's true. But it is not the compiler's job to hand-hold for simple
things.

The gcc optimization here, causing an "if"-block to be executed when its
condition does not hold, is IMHO not so simple.

Rather, it's subtle and very unexpected.

One does not expect an "if" block to be executed when it condition does
not hold, as tested or relied on within the block.

Remember, in this particular case the programmer has to be well versed
in the low-level implementation of computer arithmetic to even consider
the idea of modular behaviour of signed overflow

Contradicting your immediately preceding statement, above.

I did expect a self-contradiction sooner or later, but still.

Ordinarily there is some distance between the self-contradictory statements.

Anyway, in addition to the self-contradiction you're again conflating
things, and adding very misleading loaded descriptions like "low level":
ordinary + is what one uses, it's not "low level".

And just to top it off, in addition to self-contradiction and misleading
characterizations, if it were true that signed arithmetic is too
difficult to understand, then you're saying that gcc is optimizing away
the ability to reliably detect errors for a case where you think the
programmer has no idea that there can be an error.

- it [wrap-around] is not a natural idea for a beginner.

Any halfway decent C or C++ programmer knows about wrap-around for
signed arithmetic.

Even my young pupils at a vocational school in the early 1990's
understood this, then using Turbo Pascal and x86 assembly, and I would
absolutely not let them loose on modern C++.

In short, you're wrong.

So you are talking about programmers who have a
good understanding of low-level integer arithmetic, yet a poor
understanding of C's behaviour, an inability to use compiler warnings,
and a total lack of ability to look things up on the internet. That's a
small subset of programmers.

Oh, that devolved fast.

"good understanding" is right and OK.

"low-level" is actively misleading, because the built-in arithmetic such
as + is the arithmetic one relates to and uses directly, and so there is
nothing particularly low level about that: it is not low level compared
to ordinary arithmetic in C++, i.e. compared to itself.

"C's behavior" is incorrect and misleading. It (that is, your issue, not
mine) is about the gcc compiler's aggressive default optimization, and
we're discussing C++ in C++ group. You have yourself shown that all
that's needed for the optimizaton to kick in, if one has a g++
installation/variant that does this, is a general "-O2" optimization.

"inability to use compiler warnings" is incorrect and misleading. In
many situations code that is maintained or used causes numerous
warnings. I prefer zero diagnostics code, but in many cases that's pure
fantasy, as is your idea of the perfect infallible programmer.

"lack of ability to look up" is again just misleading, and derogatory.
The programmer who does not know about the issue, or rather the issues,
does not know what to look up. Or that there is anything to look up.

You are making absolutely no sense.

Oh dear.

[snipped further confused stuff, misdirections, etc.]


- Alf
 
I

Ian Collins

David said:
Debugging effectively is always a bit of an art. Different types of
program, different types of problem, and different types of compiler
need different strategies. There are certainly times when you want to
disable (or lower) optimisations to aid debugging.

My point is that you do /not/ want to do your testing and debugging with
different settings from your release code. (I know that "testing" and
"debugging" are different things, but they are often done at least
partly together.)

Release testing maybe, but testing during development will often be done
with different settings and even (especially for embedded targets) on a
different platform.
 
Ö

Öö Tiib

Yes, as I noted earlier - at least during early development or for
looking at specific problems. By the time you are testing the whole
thing, you should be using the same settings as your final release will use.

So please clarify where is that "common mistake" that Alf made that you
noted earlier:

Maintenance of most products is sort of constant process since everything
contains subtle shortcomings. Often less skilled developers are used as
maintainers. More skilled are needed as writers of shiny new stuff.
Maintenance causes often new defects. Tester tests with "release" build.
Maintainer debugs findings of tester with "debug" build. If the two behave
differently then there may be additional clarification needed. That
possibly takes whole additional maintenance cycle. Finally maintainer has
to debug with "release" build to find the cause of issue. That is
additional inconvenience for anyway less skilled developer.
 
A

Alf P. Steinbach

How can they "know" something which does not hold?

All known extant C++ compilers (disregarding rumors of a Z-system
compiler) give wrap-around behavior for signed arithmetic, even though
this behavior is not mandated by the standard.

That's because the underlying hardware yields this behavior.

And so this includes, of course, gcc, as has been demonstrated directly
in this thread, but for you, here's a second example using g++ (gcc):

Code:
#include <iostream>
#include <limits.h>
using namespace std;

auto main() -> int
{
int a = -1/2u;
int b = a + 1;

cout << a << " " << b << endl;
}

Compiling and running the program, in Windows:

[example]
[D:\dev\test]
version g++
g++ (GCC) 4.7.2

[D:\dev\test]
g++ foo.cpp
[D:\dev\test]
a
2147483647 -2147483648

[D:\dev\test]
[/code]

It's unclear what your vague "does not hold" is intended to mean, but
from the context I gather that you think the above example, and like
examples for as good as all (or perhaps just simply all) C++ compilers,
are made-up by folks like me in order to deceive you?

Anyway, if you know of an exception, a C or C++ compiler that does not
have two's complement signed integers, then do provide a reference.

At most they might
have a wide-spread misconception.

Perhaps you're conflating the formal guarantees of the standard with the
in-practice, as a certain Herb Schildt did regarding this matter
(although his point of view was the opposite of yours)?

Ah, it seems now we come to the crux of the point. You are conflating C++
and x86.

No, you got both history and technology wrong here.

Wrap-around behavior for signed arithmetic is a consequence of the
generally simplest and most efficient way to implement signed arithmetic
in hardware.

It's also mathematically the simplest, which is no coincidence.

Thus it was widely used on computers before the emergence of the i8086
in, IIRC, 1979.

And the C language goes back some years before that.


[snip more such stuff]

Note that x86 raises the CPU overflow flag (OF) if there was an overflow.
The C and C++ implementations on x86 just chose to ignore this flag for
signed arithmetics overflow.

Not only on x86, but as a pretty much universal convention.

The major reason has to do with the purpose of the language.


We would be in a much better condition now
if they had chosen to trap it and abort the program, at least in "debug"
build mode or whatever.

Well, given that you have your facts wrong (see above), your evaluation
of good versus bad here is better ignored. That doesn't mean that its
necessarily wrong or unfounded, but your comments so far, instead of
building a case for someone knowledgeable enough to hold an opinion on
this, has built the case for the opposite, that, in spite of offering
some facts, you don't know the basics of the subject matter. Sorry.


Cheers & hth.,

- Alf
 
A

Alf P. Steinbach

Well, I consider myself a pretty decent C++ programmer, but in my ten
years of professional C++ programming I never was in need of modular
arithmetic.

Right. It's needed mostly for some (important) corner cases. But I'm
sure that contrary to David Brown's opinion about C++ programmers in
general, that in his view they're ignorant of even such basic matters,
you did KNOW about it -- in particular since modular arithmetic is
guaranteed by the C++ standard for unsigned types.

Quite the contrary, I used to miss Ada95's ability to define
arithmetic types with upper and lower bounds (can sure be done with C++,
too, but that is not "built in" and thus there is no standard way of
doing it).




Oh gosh, I hope that you are alright. I was starting to wonder what
happened to you (didn't see you in this newsgroup). I can't imagine that
you should have trouble finding a decent job. After all, all you have to
do is telling them that they should have a look all your numerous
postings in this very newsgroup.

Thanks. :)

Just note, that since the influx of small trolls in this NG there are
probably a number of readers who thing you're being ironic, and who
think that scoring in such a way would be a good argument in a technical
debate.

They would not understand the word "fallacy" it it bit them in the ass.


Cheers & hth.,

- Alf
 
A

Alf P. Steinbach

Any halfway decent C or C++ programmer knows about wrap-around for
signed arithmetic.

How can they "know" something which does not hold?
[...]
It's unclear what your vague "does not hold" is intended to mean,

OK, I clarify. The following program aborts and dumps core on signed
overflow,

It does not do that with default options for any compiler I know of.

So no, the code does not guarantee that, and it's not the default behavior.

clearly showing that there are current C++ implementations
where signed arithmetic does not wrap around,

No, it only shows that you can get trapping behavior.

Which has not been contested, and has been mentioned a number of times,
I think by me.

After all, we're discussing the formal UB that allows that.

even on the x86 hardware.
Note that this is a fully standard-compliant behavior:

Since we (or at least I) are discussing whether the standard should or
reasonably could be changed regarding allowing that behavior, i.e. since
that fact is the very basis of the discussion,

I gather that your admonition to "Note" that, is added just to HELP
readers who happen to read this out of context, rather than being
misdirection about your chosen opponent, for such readers.

Anyway, the example does not support your earlier assertion that
wrap-around behavior "does not hold", which was and is incorrect no
matter which reasonable interpretation of "does not hold" I try.

I.e., (1) your earlier "does not hold" assertion is still false.

To that incorrect assertion you have now added two further false
assertions, namely (2) the incorrect assertion that your program
generally "aborts and dumps core", which it only does with certain
special options with certain compilers, and which is nothing new in this
discussion, and (3) the incorrect assertion that the example
demonstrates a compiler that lacks wrap-around behavior, in a follow-up
to a posting demonstrating that that very same compiler does have
wrap-around behavior, and has that as default.

Summing up, an utter disregard for reason.

Test>cat test1.cpp
#include <time.h>

int main() {
int x = time();
return x+x;
}

Test>g++ test1.cpp -ftrapv -Wall

Test>./a.exe
Aborted (core dumped)

Test>g++ --version
g++ (GCC) 4.7.3
Copyright (C) 2012 Free Software Foundation, Inc.

Well, thank you for this long discussion. It got me thinking about these
problems, and as a result I decided to add -ftrapv to my common set of
gcc flags.

That might be a good idea, or not.

A main problem is that the actual behavior then STILL is different from
gcc's optimization assumption of infinite precision arithmetic.

Which means you can still get broken assumptions, different release and
debug behavior, and so on, including now crashes in code that earlier
worked just fine... You do realize that you have to run through all test
suits again with your new flag, for all code that you intend to build
this new way? Both at the unit test level and higher.


Cheers & hth.,

- Alf
 
I

Ian Collins

David said:
Yes, as I noted earlier - at least during early development or for
looking at specific problems. By the time you are testing the whole
thing, you should be using the same settings as your final release will use.

I'll have to disagree with you there. There are many levels of testing
for most software products from developer unit tests through to formal
release testing.

Nearly all of the embedded products I have managed are developed using
host based tools and tested using simulated hardware in the same
environment. Only the final release testing is run with the target
hardware and build.

I have only encountered a handful of target only bugs using this process
and most of them were stack overflows. I'd guess that >99% of software
bugs are logic errors, so the platform and compiler settings are
irrelevant most of the time.
 
D

Dombo

Op 14-Oct-13 18:57, Paavo Helde schreef:
In my experience bugs which appear in release, but not in debug, are very
rare.

That is my experience too. The vast majority of the bugs I have had to
deal last twenty years with exhibit themselves regardless of the
compiler settings. I've seen only a very few bugs (typically caused by
UB, rarely by a compiler bug) that only exhibit themselves with specific
compiler settings.

Debugging code compiled with aggressive optimizations can be very
confusing and even misleading, especially for less experienced
programmers. If the problem also reproduces with a debug build I see no
reason why one would go through the trouble of insisting on using a
release build for debugging.

I agree with others here that testing should always be done on release
builds.
 
T

Tobias Müller

Paavo Helde said:
YMMV, of course. I have a habit of littering the code with debug-mode
asserts. As a result, the program runs 10 times slower in debug mode, but
often the reported bug triggers some assert much earlier in the code, and
then it becomes *much* easier to fix.

Not to mention all the additional tests and assertions in the debug build
of the runtime library or inserted by the compiler, like:
- checked iterators
- heap corruption detection
- uninitialized variable detection
- leak detection

Of course that does not replace proper testing, but it certainly helps to
spot bugs much earlier and easier if turned on while developing.

Tobi
 
T

Tobias Müller

Alf P. Steinbach said:
Thanks. :)

Just note, that since the influx of small trolls in this NG there are
probably a number of readers who thing you're being ironic

I have to admit that I wasn't sure how this was meant.
and who think that scoring in such a way would be a good argument in a technical debate.

Technical knowhow is not the only criterion for choosing new employees.
They would not understand the word "fallacy" it it bit them in the ass.

Others would be:
- friendly discussion style
- respect the opinions and ideas of your teammates (even if they are
younger and less experienced than you)

The small troll
 
A

Alf P. Steinbach

I have to admit that I wasn't sure how this was meant.


Technical knowhow is not the only criterion for choosing new employees.

That's a quite unfriendly comment, I think.

Of the insidious kind.

Others would be:
- friendly discussion style

Oh!

Well, right above (see comment) you are saying very unfriendly things,
and since it has nothing to do with anything technical it can only be
purely personal -- you know, ad hominem, which is a known fallacy.

But it's only a self-contradiction (another fallacy) if you are aiming
for employment, he he.

- respect the opinions and ideas of your teammates (even if they are
younger and less experienced than you)

The small troll

In Norwegian mythology the trolls don't survive exposure to daylight.

Just sayin'. ;-)


Cheers & hth.,

- Alf
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
474,099
Messages
2,570,626
Members
47,237
Latest member
David123

Latest Threads

Top