Tutorial and Motivating Examples
Arithmetic Expressions Can Yield Incorrect Results.
When some operation results in a result which exceeds the capacity
of a data variable to hold it, the result is undefined. This is called
"overflow". Since word size can differ between machines, code which
produces 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 char type with safe<char>
type.
Note that I've used char types in this example to make
the problem and solution easier to see. The exact same example could have
been done with int types albeit with different values.
Arithmetic Operations can Overflow Silently
A variation of the above is when a value is incremented/decremented
beyond it's domain. This is a common problem with for loops.
When 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 Change Data Values
A simple assignment or arithmetic expression will convert all the
terms to the same type. Sometimes this can silently change values. For
example, when a signed data variable contains a negative type, assigning
to a unsigned type will be permitted by any C/C++ compiler but will be
treated as large unsigned value. Most modern compilers will emit a compile
time warning when this conversion is performed. The user may then decide
to change some data types or apply a static_cast. This is
less than satisfactory for two reasons:
It may be unwieldy to change all the types to signed or
unsigned.
We may believe that our signed type will never contain a
negative value. static_cast changes the data type - not
the data value. If we ignore the any compiler warnings or use a
static_cast to suppress them, we'll fail to detect a
program error when it is committed. This is aways a risk with
casts.
This solution is simple, Just replace instances of the int
with safe<int>.
Mixing Data Types Can Create Subtle Errors
C++ 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 10: mixing types produces surprising results
Not using safe numerics
10000
4294957296
Using safe numerics
10000
detected error:converted negative value to unsigned
This solution is simple, Just replace instances of the
intwith safe<int>.
Array Index Value Can Exceed Array Limits
Using 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;
Collections
like standard arrays, 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 range 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 Overlooked
It's way too easy to overlook the checking of parameters received
from outside the current program.Without
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.
Programming by Contract is Too Slow
Programming by Contract is a highly regarded technique. There has
been much written about 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 guarantee parameter requirements with little or no runtime overhead.
Consider the following example:
In 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 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.