Tutorial and Motivating ExamplesArithmetic Expressions Can Yield Incorrect ResultsWhen some operation on signed integer types results in a result
which exceeds the capacity of a data variable to hold it, the result is
undefined. In the case of unsigned integer types a similar situation
results in a value wrap as per modulo arithmetic. In either case the
result is different than in integer number arithmetic in the mathematical
sense. This is called "overflow". Since word size can differ between
machines, code which produces mathematically correct results in one set of
circumstances may fail when re-compiled on a machine with different
hardware. When this occurs, Most C++ compilers will continue to execute
with no indication that the results are wrong. It is the programmer's
responsibility to ensure such undefined behavior is avoided.This program demonstrates this problem. The solution is to replace
instances of int type with safe<int>
type.example 1:undetected erroneous expression evaluation
Not using safe numerics
-2147483647 != -2147483647
error NOT detected!
Using safe numerics
addition result too large
Program ended with exit code: 0Arithmetic Operations Can Overflow SilentlyA variation of the above is when a value is incremented/decremented
beyond its domain. This is a common problem with for loops.example 2:undetected overflow in data type
Not using safe numerics
-2147483648 != 2147483647 + 1
error NOT detected!
Using safe numerics
addition result too large
error detected!
Program ended with exit code: 0When variables of unsigned integer type are decremented below zero,
they "roll over" to the highest possible unsigned version of that integer
type. This is a common problem which is generally never detected.Implicit Conversions Can Lead to Erroneous ResultsAt CPPCon 2016 Jon Kalb gave a very entertaining (and disturbing)
lightning
talk related to C++ expressions.The talk included a very, very simple example similar to the
following:example 3: implicit conversions change data values
Not using safe numerics
b is less than a
Using safe numerics
a is less than b
Program ended with exit code: 0A normal person reads the above code and has to be dumbfounded by
this. The code doesn't do what the text - according to the rules of
algebra - says it does. But C++ doesn't follow the rules of algebra - it
has its own rules. There is generally no compile time error. You can get a
compile time warning if you set some specific compile time switches. The
explanation lies in reviewing how C++ reconciles binary expressions
(a < b is an expression here) where operands are different
types. In processing this expression, the compiler:Determines the "best" common type for the two operands. In
this case, application of the rules in the C++ standard dictate that
this type will be an unsigned int.Converts each operand to this common type. The signed value of
-1 is converted to an unsigned value with the same bit-wise
contents, 0xFFFFFFFF, on a machine with 32 bit integers. This
corresponds to a decimal value of 4294967295.Performs the calculation - in this case it's
<, the "less than" operation. Since 1 is less than
4294967295 the program prints "b is less than a".In order for a programmer to detect and understand this error he
should be pretty familiar with the implicit conversion rules of the C++
standard. These are available in a copy of the standard and also in the
canonical reference book The C++
Programming Language (both are over 1200 pages long!).
Even experienced programmers won't spot this issue and know to take
precautions to avoid it. And this is a relatively easy one to spot. In the
more general case this will use integers which don't correspond to easily
recognizable numbers and/or will be buried as a part of some more complex
expression.This example generated a good amount of web traffic along with
everyone's pet suggestions. See for example a blog
post with everyone's favorite "solution". All the proposed
"solutions" have disadvantages and attempts to agree on how handle this
are ultimately fruitless in spite of, or maybe because of, the emotional
content. Our solution is by far the simplest: just use the safe
numerics library as shown in the example above.Note that in this particular case, there is absolutely no extra
overhead in using the safe integer library. Code generated will either
equal or exceed the efficiency of using primitive integer types.Mixing Data Types Can Create Subtle ErrorsC++ contains signed and unsigned integer types. In spite of their
names, they function differently which often produces surprising results
for some operands. Program errors from this behavior can be exceedingly
difficult to find. This has lead to recommendations of various ad hoc
"rules" to avoid these problems. It's not always easy to apply these
"rules" to existing code without creating even more bugs. Here is a
typical example of this problem:Here
is the output of the above program:example 4: mixing types produces surprising results
Not using safe numerics
10000
4294957296
Using safe numerics
10000
detected error:converted negative value to unsigned
Program ended with exit code: 0This solution is simple, just replace
instances of int with safe<int>.Array Index Value Can Exceed Array LimitsUsing an intrinsic C++ array, it's very easy to exceed array limits.
This can fail to be detected when it occurs and create bugs which are hard
to find. There are several ways to address this, but one of the simplest
would be to use safe_unsigned_range;example 5: array index values can exceed array bounds
Not using safe numerics
error NOT detected!
Using safe numerics
Value out of range for this safe type
error detected!
Program ended with exit code: 0Collections like standard
arrays and vectors do array index checking in some function calls and not
in others so this may not be the best example. However it does illustrate
the usage of safe_range<T> for assigning legal ranges
to variables. This will guarantee that under no circumstances will the
variable contain a value outside of the specified range.Checking of Input Values Can Be Easily OverlookedIt's way too easy to overlook the checking of parameters received
from outside the current program.example 6: checking of externally produced value can be overlooked
Not using safe numerics
2147483647 0
error NOT detected!
Using safe numerics
error in file input
error detected!
Program ended with exit code: 0Without safe integer, one will
have to insert new code every time an integer variable is retrieved. This
is a tedious and error prone procedure. Here we have used program input.
But in fact this problem can occur with any externally produced
input.Cannot Recover From Arithmetic ErrorsIf a divide by zero error occurs in a program, it's detected by
hardware. The way this manifests itself to the program can and will depend
upondata type - int, float, etcsetting of compile time command line switchesinvocation of some configuration functions which convert these
hardware events into C++ exceptionsIt's not all that clear how one would detect and recover
from a divide by zero error in a simple portable way. Usually, users just
ignore the issue which usually results in immediate program termination
when this situation occurs.This library will detect divide by zero errors before the operation
is invoked. Any errors of this nature are handled according to the
ErrorPolicy selected by the library user.example 7: cannot recover from arithmetic errors
Not using safe numerics
error NOT detectable!
Using safe numerics
divide by zero
error detected!
Program ended with exit code: 0Programming by Contract is Too SlowProgramming by Contract is a highly regarded technique. There has
been much written about it and it has been proposed as an addition to the
C++ language GarciaCrowl &
Ottosen. It (mostly) depends upon runtime checking of parameter
and object values upon entry to and exit from every function. This can
slow the program down considerably which in turn undermines the main
motivation for using C++ in the first place! One popular scheme for
addressing this issue is to enable parameter checking only during
debugging and testing which defeats the guarantee of correctness which we
are seeking here! Programming by Contract will never be accepted by
programmers as long as it is associated with significant additional
runtime cost.The Safe Numerics Library has facilities which, in many cases, can
check guaranteed parameter requirements with little or no runtime
overhead. Consider the following example:example 8: enforce contracts with zero runtime cost
parameter error detected
Program ended with exit code: 0In the example above, the function convert incurs
significant runtime cost every time the function is called. By using
"safe" types, this cost is moved to the moment when the parameters are
constructed. Depending on how the program is constructed, this may totally
eliminate extraneous computations for parameter requirement type checking.
In this scenario, there is no reason to suppress the checking for release
mode and our program can be guaranteed to be always arithmetically
correct.