#024 – The dangers of using unsigned ints

Unsigned int‘s are strictly positive.

It gets double the positive range than its signed counterpart.

It seems like a free upgrade.

However, unsigned integers introduce a class of bugs that are easy to write, difficult to detect, and silently destructive. Google’s C++ style guide goes as far as advising engineers to avoid them entirely outside of bitfields and modular arithmetic.

The basics

Size/TypeUnsigned RangeSigned Range
8 bit0 to 255-128 to 127
16 bit0 to 65,535-32,768 to 32,767
32 bit0 to 4,294,967,295-2,147,483,648 to 2,147,483,647

When we do int x { 11};, the compiler implicitly creates a signed int. However, if we want to make an unsigned int, we have to be explicit. There are 2 ways to declare an unsigned int. We can either use the unsigned int x keyword or use the fixed-width types from <cstdint>, uint8_t x;

#include <cstdint> // Required for uint8_t

int main() {
	unsigned int x {11}; // 1. Using unsigned keyword
	std::uint32_t y {11}; // 2. Using fixed-width int type
}

With that out of the day, let’s discuss the bugs that unsigned ints introduce.

1. Unsigned int‘s can cause overflow in both directions

Unlike their signed counterparts, unsigned int‘s can overflow in the positive and negative direction. This means you must be vigilant for integer overflows from two areas.

Additionally, since negative overflows are easier to accidentally make, it makes your program more error-prone.

Here’s a simple example

#include <iostream>

int main() {

	unsigned int y {3};
	unsigned int x {4};
	std::cout << y - x << std::endl;
}

// Console output
4294967295

For performance reasons, it is superior to iterate through a loop by decrementing rather than incrementing the loop variable. This opens the door for an easy integer overflow.

#include <iostream>
#include <vector>

void process_data(const std::vector<int>& vec) {
    // We use size_t because vec.size() returns size_t (unsigned)
    for (size_t i = vec.size() - 1; i >= 0; --i) {
        std::cout << "Processing index: " << i << " Value: " << vec[i] << std::endl;
    }
}

int main() {
    std::vector<int> data = {10, 20, 30};
    process_data(data);
}

// Output
Processing index: 2 Value: 30
Processing index: 1 Value: 20
Processing index: 0 Value: 10
Processing index: 18446744073709551615 Value: 0

In the 3rd loop iteration, after we print the 3rd element, we decrement the loop variable. Now i == 0. Since i is unsigned and can’t support negative values, it wraps around to the largest number a size_t can hold which is 18,446,744,073,709,551,615.

i wraps around to std::numeric_limit<std::size_t>::max()

Afterwards, the loop attempts to access vec[18446744073709551615], leading to a segmentation fault. Yikes!

2. Implicit conversions

If you perform an operation where the operands contain unsigned and signed ints, the signed variable gets promoted (implicitly converted) to unsigned.

#include <iostream>

int main() {

	unsigned int ab {9};
	int bc {12};
	
	std::cout << ab - bc;
}

// Console output
4294967295

The expected value is -3. However, bc was implicitly converted into unsigned. Hence, an overflow occurred.

The crazy thing is this is perfectly legal according to the compiler!

3. When the parameter is an unsigned int

Let’s look at a more pernicious example.

#include <iostream>

void doSomethingSpecial(unsigned int x) {
    std::cout << "The value of x is: " << x << std::endl;
}

int main()
{
    doSomethingSpecial(-1); // We're passing a signed value despite
	                        // the parameter being unsigned
}

// Console log
The value of x is: 4294967295

Ultimately…

The examples presented above are difficult to prevent from happening and difficult to enforce. Additionally, the compiler lets this occur without even telling us.

Because unsigned ints cause easy problems, produce unexpected behaviour and making debugging a nightmare, they should be avoided.

Although, this is not an argument to avoid their use entirely. They are appropriate in resource-constrained environments such as embedded systems.

Leave a Reply

Your email address will not be published. Required fields are marked *