views:

976

answers:

8

In several modern programming languages (including C++, Java, and C#), the language allows integer overflow to occur at runtime without raising any kind of error condition.

For example, consider this (contrived) C# method, which does not account for the possibility of overflow/underflow. (For brevity, the method also doesn't handle the case where the specified list is a null reference.)

//Returns the sum of the values in the specified list.
private static int sumList(List<int> list)
{
    int sum = 0;
    foreach (int listItem in list)
    {
        sum += listItem;
    }
    return sum;
}

If this method is called as follows:

List<int> list = new List<int>();
list.Add(2000000000);
list.Add(2000000000);
int sum = sumList(list);

An overflow will occur in the sumList() method (because the "int" type in C# is a 32-bit signed integer, and the sum of the values in the list exceeds the value of the maximum 32-bit signed integer). The sum variable will have a value of -294967296 (not a value of 4000000000); this most likely is not what the (hypothetical) developer of the sumList method intended.

Obviously, there are various techniques that can be used by developers to avoid the possibility of integer overflow, such as using a type like Java's BigInteger, or the checked keyword and /checked compiler switch in C#.

However, the question that I'm interested in is why these languages were designed to by default allow integer overflows to happen in the first place, instead of, for example, raising an exception when an operation is performed at runtime that would result in an overflow. It seems like such behavior would help avoid bugs in cases where a developer neglects to account for the possibility of overflow when writing code that performs an arithmetic operation that could result in overflow. (These languages could have included something like an "unchecked" keyword that could designate a block where integer overflow is permitted to occur without an exception being raised, in those cases where that behavior is explicitly intended by the developer; C# actually does have this.)

Does the answer simply boil down to performance -- the language designers didn't want their respective languages to default to having "slow" arithmetic integer operations where the runtime would need to do extra work to check whether an overflow occurred, on every applicable arithmetic operation -- and this performance consideration outweighed the value of avoiding "silent" failures in the case that an inadvertent overflow occurs?

Are there other reasons for this language design decision as well, other than performance considerations?

+15  A: 

I think performance is a pretty good reason. If you consider every instruction in a typical program that increments an integer, and if instead of the simple op to add 1, it had to check every time if adding 1 would overflow the type, then the cost in extra cycles would be pretty severe.

David Hill
+3  A: 

It is likely 99% performance. On x86 would have to check the overflow flag on every operation which would be a huge performance hit.

The other 1% would cover those cases where people are doing fancy bit manipulations or being 'imprecise' in mixing signed and unsigned operations and want the overflow semantics.

Rob Walker
+3  A: 

Because checking for overflow takes time. Each primitive mathematical operation, which normally translates into a single assembly instruction would have to include a check for overflow, resulting in multiple assembly instructions, potentially resulting in a program that is several times slower.

Dima
A: 

My understanding of why errors would not be raised by default at runtime boils down to the legacy of desiring to create programming languages with ACID-like behavior. Specifically, the tenet that anything that you code it to do (or don't code), it will do (or not do). If you didn't code some error handler, then the machine will "assume" by virtue of no error handler, that you really want to do the ridiculous, crash-prone thing you're telling it to do.

(ACID reference: http://en.wikipedia.org/wiki/ACID)

devinmoore
I don't see any connection between ACID properties and what you are describing. Please explain the connection ...
Stephen C
+2  A: 

Backwards compatibility is a big one. With C, it was assumed that you were paying enough attention to the size of your datatypes that if an over/underflow occurred, that that was what you wanted. Then with C++, C# and Java, very little changed with how the "built-in" data types worked.

Eclipse
+6  A: 

You work under the assumption that integer overflow is always undesired behavior.

Sometimes integer overflow is desired behavior. One example I've seen is representation of a an absolute heading value as a fixed point number. Given an unsigned int, 0 is 0 or 360 degrees and the max 32 bit unsigned integer(0xffffffff)is the biggest value just below 360 degrees.

int main()
{
    uint32_t shipsHeadingInDegrees= 0;

    // Rotate by a bunch of degrees
    shipsHeadingInDegrees+= 0x80000000; // 180 degrees
    shipsHeadingInDegrees+= 0x80000000; // another 180 degrees, overflows 
    shipsHeadingInDegrees+= 0x80000000; // another 180 degrees

    // Ships heading now will be 180 degrees
    cout << "Ships Heading Is" << (double(shipsHeadingInDegrees) / double(0xffffffff)) * 360.0 << std::endl;

}

There are probably other situations where overflow is acceptable, similar to this example.

Doug T.
There are probably many more instances where integer overflow yields wrong results then when it yeilds the correct result. In fact, the only time it actually generates correct results are when it's the expected behaviour, and it is actually being used as a feature, such as in your example.
Kibbee
@Kibbee I'd be willing to venture that in the case where integer overflow can lead to error, it's more an indicator that you haven't done proper range checking than a failure of the programming language. In the cases where it's not expected, your code should check for it.
OwenP
Not to mention the inherent hazard of relying on a platform-specific low-level detail like this. What happens if this is recompiled on a 64-bit platform? Eek.
Dan
@Dan: Note that the code uses uint32_t. That is guaranteed to be 32bit (hence the name :-) ). So the author *did* think of that point.
sleske
@sleske: If you check the edits, you'll see that Doug's original post used "unsigned int"...hence my comment.Perhaps my comment caused him to correct his code...
Dan
+3  A: 

C/C++ never mandate trap behaviour. Even the obvious division by 0 is undefined behaviour in C++, not a specified kind of trap.

The C language doesn't have any concept of trapping, unless you count signals.

C++ has a design principle that it doesn't introduce overhead not present in C unless you ask for it. So Stroustrup would not have wanted to mandate that integers behave in a way which requires any explicit checking. Although he did mandate modulo arithmetic, which I guess is extra work on rare architectures.

C++ also wants to support implementations of subsets of itself. So some early compilers, and lightweight implementations for restricted hardware, don't support exceptions at all, and they can often be disabled with compiler options. Mandating exceptions for language built-ins would be problematic.

Even if C++ had made integers checked, 99% of programmers in the early days would have turned if off for the performance boost...

Steve Jessop
+4  A: 

In C#, it was a question of performance. Specifically, out-of-box benchmarking.

When C# was new, Microsoft was hoping a lot of C++ developers would switch to it. They knew that many C++ folks thought of C++ as being fast, especially faster than languages that "wasted" time on automatic memory management and the like.

Both potential adopters and magazine reviewers are likely to get a copy of the new C#, install it, build a trivial app that no one would ever write in the real world, run it in a tight loop, and measure how long it took. Then they'd make a decision for their company or publish an article based on that result.

The fact that their test showed C# to be slower than natively compiled C++ is the kind of thing that would turn people off C# quickly. The fact that your C# app is going to catch overflow/underflow automatically is the kind of thing that they might miss. So, it's off by default.

I think it's obvious that 99% of the time we want /checked to be on. It's an unfortunate compromise.

Jay Bazuzi