...one of the most highly
regarded and expertly designed C++ library projects in the
world.
— Herb Sutter and Andrei
Alexandrescu, C++
Coding Standards
Safe Numerics |
At 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:
// Copyright (c) 2018 Robert Ramey // // Distributed under the Boost Software License, Version 1.0. (See // accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #include <iostream> #include <boost/safe_numerics/safe_integer.hpp> int main(){ std::cout << "example 4: "; std::cout << "implicit conversions change data values" << std::endl; std::cout << "Not using safe numerics" << std::endl; // problem: implicit conversions change data values try{ signed int a{-1}; unsigned int b{1}; std::cout << "a is " << a << " b is " << b << '\n'; if(a < b){ std::cout << "a is less than b\n"; } else{ std::cout << "b is less than a\n"; } std::cout << "error NOT detected!" << std::endl; } catch(const std::exception &){ // never arrive here - just produce the wrong answer! std::cout << "error detected!" << std::endl; return 1; } // solution: replace int with safe<int> and unsigned int with safe<unsigned int> std::cout << "Using safe numerics" << std::endl; try{ using namespace boost::safe_numerics; safe<signed int> a{-1}; safe<unsigned int> b{1}; std::cout << "a is " << a << " b is " << b << '\n'; if(a < b){ std::cout << "a is less than b\n"; } else{ std::cout << "b is less than a\n"; } std::cout << "error NOT detected!" << std::endl; return 1; } catch(const std::exception & e){ // never arrive here - just produce the correct answer! std::cout << e.what() << std::endl; std::cout << "error detected!" << std::endl; } return 0; }
example 3: implicit conversions change data values Not using safe numerics a is -1 b is 1 b is less than a error NOT detected! Using safe numerics a is -1 b is 1 converted negative value to unsigned: domain error error detected!
A 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, usage of the safe types results in no runtime overhead in using the safe integer library. Code generated will either equal or exceed the efficiency of using primitive integer types.