In this article, I thoroughly explore the extremely useful
unchecked keywords which control overflow checking. It is slanted towards someone familiar with C and/or C++.
Being able to control overflow checking is one of the many ways that C# improves on C/C++ (and other languages). In theory, C/C++ compilers can do overflow checking for signed integer arithmetic, but the behavior is "implementation defined" according to the C standard. All compilers that I am aware of take the easy way out and ignore overflow conditions - as long as they document the behavior they are considered to be standard conforming. (However, if overflow can occur, you should always use unsigned integer types in C/C++, which are explicitly defined to have unchecked behavior on overflow.)
Most of the time, overflow does not occur, but you sometimes want to check for overflows in case your code has bugs. However, where C# is really useful is when you are expecting a possible overflow. I will discuss all situations in the next sections.
Overflow Not Expected
Often overflow is unimportant since it will not occur. In most code, integer variables take on a very small range of small values. I guess this is why most C/C++ compilers ignore the possibility of overflow.
Of course, even if you don't expect an overflow condition, it may still occur as a result of a bug. For example, it is easy in C to create an infinite loop by attempting to decrement an unsigned integer below zero. In C#, this would generate an overflow exception if overflow checking was on.
In that case, why not always turn overflow checking on when using C#? Well, there is a performance penalty associated with overflow checking. You may not want to slow down your code with overflow checking if you are sure that overflow can never occur.
My recommendation is to never use
unchecked if you do not expect an overflow condition. In the release build, turn off overflow checking. Turn it on for debug builds to facilitate spotting and isolating bugs during your testing.
Note that Visual Studio does not turn on overflow checking by default in either release or debug builds. You need to turn on the Check for Arithmetic Overflow setting in the Build Properties for the project - but make sure you have the Debug configuration selected when you do this.
There are two situations where overflow is expected:
- Overflow needs to be ignored in some algorithms.
- Overflow is an error condition (but not a bug) that needs to be explicitly handled.
We will look at the first situation now, and the second situation in the next section.
Many algorithms rely on overflow being ignored. Examples include checksums, and many random number and encryption algorithms. In other words, they rely on arithmetic being performed modulus 2^32 (for 32 bit integers), where any high bits generated are simply discarded.
I once had to translate a PRNG (pseudo random number generator) from C to Pascal. Unfortunately, Pascal always does overflow checking. (I could find no way to turn it off with the compiler I was using.) What was about three lines of code in C expanded to about 2 pages of Pascal code. The extra code was simply to avoid causing an overflow. (Even then, I was not certain the Pascal code would handle all conditions, but I knew the 3 lines of C code would work just by glancing at them.)
Many C/C++ programmers will not realize, at first, how useful the
unchecked keyword is because they are used to expressions never being checked, often without even realizing it. For example, I have seen many implementations of
GetHashCode that can cause an overflow. Here is an example that correctly turns off overflow checking in the calculation of a hash code.
public override int GetHashCode()
return unchecked(name.GetHashCode() + address.GetHashCode());
The second situation, mentioned above, is where you want to detect (not ignore) overflow. It often occurs where the integer values being used are entered or somehow depend on user input. For example, I encountered this situation when I added a calculator to my hex editor (see http://www.hexedit.com).
The calculator needed to tell the user when it has performed an operation that caused an overflow. Unfortunately, the code was in C++ and the compiler provided no support for overflow checking. To detect overflows, the code had to examine the operand(s) for each of the operations and try to determine if an overflow was going to happen. The code to do this is not trivial and increases the chances of new bugs being introduced. In fact, the only known bug that has ever caused HexEdit to crash was due to a divide by zero (only when the user attempted to multiply by zero) in code that was trying to detect when a multiplication in the calculator would cause an overflow!
This is the sort of situation where the C#
checked keyword is brilliant. It would be a simple matter of placing all the calculations in a
checked block to turn on overflow checking, then wrap the whole thing in a
try block that catches
Traps For the Unwary
By now, you may realize that I like the control C# gives over overflow checking. However, there are a few vagaries that you should be aware of.
First, it only works for integer arithmetic. Many may expect it to work for floats, decimals etc., but it doesn't. In fact, overflow in floating point numbers (
double) will never throw an exception but simply return a special value of +/- Infinity. On the other hand, for the
decimal type, the
unchecked keywords are also ignored but overflow always throws an
Another thing to watch is that, even for integers, the
unchecked keywords have no effect when division by zero is attempted - a
DivideByZeroException is always thrown. This may be surprising to some, as they might consider division by zero to be simply a special case of overflow. However, in this case, there is no logical value for the result - even in C, dividing an integer (signed or unsigned) by zero is an error that typically terminates the program.
As an aside, 0/0 (zero divided by zero) for integers does not cause an error (at least on my machine), but gives a result of zero. According to the C# documentation, it should throw a
DivideByZeroException. Even more interesting is that the
decimal type does throw
DivideByZeroException for 0/0. (0/0 for
double generate a special value called a NaN.)
Conversions between numeric types, where the value to be converted overflows the destination type, can also be
unchecked but only if the source is of floating point or integer type and the result is an integer type. If either of the source or destination is a
decimal type, the
unchecked keywords are ignored and overflow checking is always done. All other conversions will not cause an overflow (although there may be loss of precision), except converting a
double to a
float which generates a NaN (whereas +/- Infinity might have been expected for consistency).
Another discrepancy is that overflow checking works for simple arithmetic operations (addition, subtraction, and multiplication), but no overflow checking is performed on left shift operations.
uint a = uint.MaxValue;
uint b = checked(a << 1);
uint c = checked(a * 2);
Since a shift is effectively multiplication by a power of 2, you might expect the above (
*) operations to behave identically. However, only the multiplication will cause an exception, while the shift just gives the same result as if the expression were
unchecked. I find, this is yet another inconsistency.
An even bigger trap is that the
unchecked statements only apply to the code within the enclosed statement block or expression, and not to any nested function calls. It is important to realize that these keywords only change the code generated by the C# compiler; they do not set or clear any overflow "flag" at run-time. The temptation is to assume that they work similarly to other statements - for example, a
try block will catch exceptions generated within any nested function calls. Programmers with a C/C++ background find the syntax misleading - in C/C++, a
#pragma would typically be used for this sort of situation.
checked blocks are rather like C#
unsafe blocks, in that they control code generation, not run-time behavior. The difference with
unsafe blocks is that the compiler will generate an error if you try to call a "safe" function from within an
unsafe block. But the C# compiler does not even warn if you try to call a function with
unchecked expressions from within a
checked block, or vice versa.
int square(int i)
return i * i;
int i = square(1000000);
The code in
square() will not be checked when called from
f() (unless the global compiler checking flag is on). There is no run-time "flag" to control overflow checking, so a function (like
square() above) cannot change its run-time behavior depending on where it is called from. Hence, the above code will not throw an overflow exception as might be expected.
Note that you can inspect the compiled code to see how it changes by looking at the IL code using ILDASM. There are two variations of the relevant commands, without and with overflow checking. For example, the multiply commands are called
mul.ovf. (Actually, there are two multiply commands with overflow checking:
mul.ovf.un for signed and unsigned integers.)
In summary, the C# control of overflow handling is extremely useful where there is possibility of overflow.
My rules of thumb for its use are:
- Don't use the
checked keywords unless an overflow condition is possible.
unchecked when you expect overflow but want to ignore it.
checked where it is a possible error condition which you want to catch.
- Turn on overflow checking globally in debug builds to detect bugs.
- Turn off overflow checking globally in release builds for efficiency.
unchecked only work on the enclosed statements and do not affect nested function calls. Also they only work for simple integer arithmetic (and not shifts) and only for conversions from a real/integer type to a smaller integer type. For the
decimal type, overflow checking is always done irrespective of any
unchecked keyword, whereas pure floating-point operations are never checked.
Andrew has a BSc (1983) from Sydney University in Computer Science and Mathematics. Andrew began programming professionally in C in 1984 and has since used many languages but mainly C and C++.
Andrew has a particular interest in STL, user interface design and .Net. He has written articles on STL for technical journals such as the C/C++ User's Journal.
In 1997 Andrew began using MFC and released the source code for a Windows binary file editor called HexEdit, which was downloaded more than 1 million times. Since then he released a shareware version which is updated regularly (see http://www.hexedit.com). A new open source version using the wonderful new MFC is in the works.